View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.HashSet;
6   import java.util.List;
7   import java.util.Set;
8   
9   import nl.tudelft.simulation.language.d3.DirectedPoint;
10  
11  import org.djunits.unit.AccelerationUnit;
12  import org.djunits.unit.TimeUnit;
13  import org.djunits.value.vdouble.scalar.Acceleration;
14  import org.djunits.value.vdouble.scalar.Length;
15  import org.djunits.value.vdouble.scalar.Time;
16  import org.opentrafficsim.core.gtu.GTU;
17  import org.opentrafficsim.core.gtu.GTUDirectionality;
18  import org.opentrafficsim.core.gtu.GTUException;
19  import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
20  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
21  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.Segment;
22  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
23  import org.opentrafficsim.core.network.LateralDirectionality;
24  import org.opentrafficsim.core.network.NetworkException;
25  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
26  import org.opentrafficsim.road.gtu.lane.driver.LaneBasedBehavioralCharacteristics;
27  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
28  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedAltruistic;
29  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedEgoistic;
30  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneChangeModel;
31  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneMovementStep;
32  import org.opentrafficsim.road.gtu.lane.tactical.following.AccelerationStep;
33  import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
34  import org.opentrafficsim.road.gtu.lane.tactical.following.HeadwayGTU;
35  import org.opentrafficsim.road.network.lane.Lane;
36  
37  /**
38   * Lane-based tactical planner that implements car following behavior and rule-based lane change. This tactical planner
39   * retrieves the car following model from the strategical planner and will generate an operational plan for the GTU.
40   * <p>
41   * A lane change occurs when:
42   * <ol>
43   * <li>The route indicates that the current lane does not lead to the destination; main choices are the time when the GTU
44   * switches to the "right" lane, and what should happen when the split gets closer and the lane change has failed. Observations
45   * indicate that vehicles if necessary stop in their current lane until they can go to the desired lane. A lane drop is
46   * automatically part of this implementation, because the lane with a lane drop will not lead to the GTU's destination.</li>
47   * <li>The desired speed of the vehicle is a particular delta-speed higher than its predecessor, the headway to the predecessor
48   * in the current lane has exceeded a certain value, it is allowed to change to the target lane, the target lane lies on the
49   * GTU's route, and the gap in the target lane is acceptable (including the evaluation of the perceived speed of a following GTU
50   * in the target lane).</li>
51   * <li>The current lane is not the optimum lane given the traffic rules (for example, to keep right), the headway to the
52   * predecessor on the target lane is greater than a certain value, the speed of the predecessor on the target lane is greater
53   * than or equal to our speed, the target lane is on the route, it is allowed to switch to the target lane, and the gap at the
54   * target lane is acceptable (including the perceived speed of any vehicle in front or behind on the target lane).</li>
55   * </ol>
56   * <p>
57   * This lane-based tactical planner makes decisions based on headway (GTU following model). It can ask the strategic planner for
58   * assistance on the route to take when the network splits.
59   * <p>
60   * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
61   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
62   * </p>
63   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
64   * initial version Nov 25, 2015 <br>
65   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
66   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
67   */
68  public class LaneBasedGTUFollowingChange0TacticalPlanner extends AbstractLaneBasedTacticalPlanner
69  {
70      /** */
71      private static final long serialVersionUID = 20160129L;
72  
73      /** Earliest next lane change time. */
74      private Time.Abs earliestNexLaneChangeTime = Time.Abs.ZERO;
75  
76      /** Lane we changed to at instantaneous lane change. */
77      private Lane referenceLane = null;
78  
79      /** Position on the reference lane. */
80      private Length.Rel referencePos = null;
81  
82      /** When a failure in planning occurs, should we destroy the GTU to avoid halting of the model? */
83      private boolean destroyGtuOnFailure = false;
84  
85      /**
86       * Instantiated a tactical planner with just GTU following behavior and no lane changes.
87       */
88      public LaneBasedGTUFollowingChange0TacticalPlanner()
89      {
90          super();
91      }
92  
93      /** {@inheritDoc} */
94      @Override
95      public OperationalPlan generateOperationalPlan(final GTU gtu, final Time.Abs startTime,
96          final DirectedPoint locationAtStartTime) throws OperationalPlanException, NetworkException, GTUException
97      {
98          try
99          {
100             // ask Perception for the local situation
101             LaneBasedGTU laneBasedGTU = (LaneBasedGTU) gtu;
102             LanePerception perception = laneBasedGTU.getPerception();
103             LaneBasedBehavioralCharacteristics drivingCharacteristics = laneBasedGTU.getBehavioralCharacteristics();
104 
105             // start with the turn indicator off -- this can change during the method
106             laneBasedGTU.setTurnIndicatorStatus(TurnIndicatorStatus.NONE);
107 
108             // if the GTU's maximum speed is zero (block), generate a stand still plan for one second
109             if (laneBasedGTU.getMaximumVelocity().si < OperationalPlan.DRIFTING_SPEED_SI)
110             {
111                 return new OperationalPlan(gtu, locationAtStartTime, startTime, new Time.Rel(1.0, TimeUnit.SECOND));
112             }
113 
114             // perceive the forward headway, accessible lanes and speed limit.
115             perception.updateForwardHeadwayGTU();
116             perception.updateAccessibleAdjacentLanesLeft();
117             perception.updateAccessibleAdjacentLanesRight();
118             perception.updateSpeedLimit();
119 
120             // find out where we are going
121             Length.Rel forwardHeadway = drivingCharacteristics.getForwardHeadwayDistance();
122             LanePathInfo lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway);
123             NextSplitInfo nextSplitInfo = determineNextSplit(laneBasedGTU, forwardHeadway);
124             Set<Lane> correctLanes = laneBasedGTU.getLanes().keySet();
125             correctLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
126 
127             // Step 1: Do we want to change lanes because of the current lane not leading to our destination?
128             if (lanePathInfo.getPath().getLength().lt(forwardHeadway))
129             {
130                 if (correctLanes.isEmpty())
131                 {
132                     LateralDirectionality direction = determineLeftRight(laneBasedGTU, nextSplitInfo);
133                     if (direction != null)
134                     {
135                         gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
136                         if (canChange(laneBasedGTU, perception, lanePathInfo, direction))
137                         {
138                             DirectedPoint newLocation = changeLane(laneBasedGTU, direction);
139                             lanePathInfo =
140                                 buildLanePathInfo(laneBasedGTU, this.referenceLane, this.referencePos,
141                                     forwardHeadway);
142                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
143                         }
144                     }
145                 }
146             }
147 
148             // Condition, if we have just changed lane, let's not change immediately again.
149             // TODO make direction dependent!
150             if (gtu.getSimulator().getSimulatorTime().getTime().lt(this.earliestNexLaneChangeTime))
151             {
152                 return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
153             }
154 
155             // Step 2. Do we want to change lanes to the left because of our predecessor on the current lane?
156             // does the lane left of us [TODO: driving direction] bring us to our destination as well?
157             Set<Lane> leftLanes = perception.getAccessibleAdjacentLanesLeft().get(lanePathInfo.getReferenceLane());
158             if (nextSplitInfo.isSplit())
159             {
160                 leftLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
161             }
162             if (!leftLanes.isEmpty() && laneBasedGTU.getVelocity().si > 4.0) // XXX we are driving...
163             {
164                 perception.updateBackwardHeadwayGTU();
165                 perception.updateParallelGTUsLeft();
166                 perception.updateLaneTrafficLeft();
167                 if (perception.getParallelGTUsLeft().isEmpty())
168                 {
169                     Collection<HeadwayGTU> sameLaneTraffic = new HashSet<>();
170                     if (perception.getForwardHeadwayGTU() != null
171                         && perception.getForwardHeadwayGTU().getGtuId() != null)
172                     {
173                         sameLaneTraffic.add(perception.getForwardHeadwayGTU());
174                     }
175                     if (perception.getBackwardHeadwayGTU() != null
176                         && perception.getBackwardHeadwayGTU().getGtuId() != null)
177                     {
178                         sameLaneTraffic.add(perception.getBackwardHeadwayGTU());
179                     }
180                     DirectedLaneChangeModel dlcm = new DirectedAltruistic();
181                     DirectedLaneMovementStep dlms =
182                         dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.LEFT,
183                             sameLaneTraffic, perception.getNeighboringGTUsLeft(), laneBasedGTU
184                                 .getBehavioralCharacteristics().getForwardHeadwayDistance(), perception.getSpeedLimit(),
185                             new Acceleration(1.0, AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI),
186                             new Time.Rel(0.5, TimeUnit.SECOND));
187                     if (dlms.getLaneChange() != null)
188                     {
189                         gtu.setTurnIndicatorStatus(TurnIndicatorStatus.LEFT);
190                         if (canChange(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.LEFT))
191                         {
192                             DirectedPoint newLocation = changeLane(laneBasedGTU, LateralDirectionality.LEFT);
193                             lanePathInfo =
194                                 buildLanePathInfo(laneBasedGTU, this.referenceLane, this.referencePos,
195                                     forwardHeadway);
196                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
197                         }
198                     }
199                 }
200             }
201 
202             // Step 3. Do we want to change lanes to the right because of TODO traffic rules?
203             Set<Lane> rightLanes = perception.getAccessibleAdjacentLanesRight().get(lanePathInfo.getReferenceLane());
204             if (nextSplitInfo.isSplit())
205             {
206                 rightLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
207             }
208             if (!rightLanes.isEmpty() && laneBasedGTU.getVelocity().si > 4.0) // XXX we are driving...
209             {
210                 perception.updateBackwardHeadwayGTU();
211                 perception.updateParallelGTUsRight();
212                 perception.updateLaneTrafficRight();
213                 if (perception.getParallelGTUsRight().isEmpty())
214                 {
215                     Collection<HeadwayGTU> sameLaneTraffic = new HashSet<>();
216                     if (perception.getForwardHeadwayGTU() != null
217                         && perception.getForwardHeadwayGTU().getGtuId() != null)
218                     {
219                         sameLaneTraffic.add(perception.getForwardHeadwayGTU());
220                     }
221                     if (perception.getBackwardHeadwayGTU() != null
222                         && perception.getBackwardHeadwayGTU().getGtuId() != null)
223                     {
224                         sameLaneTraffic.add(perception.getBackwardHeadwayGTU());
225                     }
226                     DirectedLaneChangeModel dlcm = new DirectedAltruistic();
227                     DirectedLaneMovementStep dlms =
228                         dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.RIGHT,
229                             sameLaneTraffic, perception.getNeighboringGTUsRight(), laneBasedGTU
230                                 .getBehavioralCharacteristics().getForwardHeadwayDistance(), perception.getSpeedLimit(),
231                             new Acceleration(1.0, AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI),
232                             new Time.Rel(0.5, TimeUnit.SECOND));
233                     if (dlms.getLaneChange() != null)
234                     {
235                         gtu.setTurnIndicatorStatus(TurnIndicatorStatus.RIGHT);
236                         if (canChange(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.RIGHT))
237                         {
238                             DirectedPoint newLocation = changeLane(laneBasedGTU, LateralDirectionality.RIGHT);
239                             lanePathInfo =
240                                 buildLanePathInfo(laneBasedGTU, this.referenceLane, this.referencePos,
241                                     forwardHeadway);
242                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
243                         }
244                     }
245                 }
246             }
247 
248             return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
249         }
250         catch (GTUException | NetworkException | OperationalPlanException exception)
251         {
252             if (isDestroyGtuOnFailure())
253             {
254                 System.err.println("LaneBasedGTUFollowingChange0TacticalPlanner.generateOperationalPlan() failed for "
255                     + gtu + " because of " + exception.getMessage() + " -- GTU destroyed");
256                 gtu.destroy();
257                 return new OperationalPlan(gtu, locationAtStartTime, startTime, new Time.Rel(1.0, TimeUnit.SECOND));
258             }
259             throw exception;
260         }
261     }
262 
263     /**
264      * Make a plan for the current lane.
265      * @param laneBasedGTU the gtu to generate the plan for
266      * @param startTime the time from which the new operational plan has to be operational
267      * @param locationAtStartTime the location of the GTU at the start time of the new plan
268      * @param lanePathInfo the lane path for the current lane.
269      * @return An operation plan for staying in the current lane.
270      * @throws OperationalPlanException when there is a problem planning a path in the network
271      * @throws GTUException when there is a problem with the state of the GTU when planning a path
272      */
273     private OperationalPlan currentLanePlan(final LaneBasedGTU laneBasedGTU, final Time.Abs startTime,
274         final DirectedPoint locationAtStartTime, final LanePathInfo lanePathInfo) throws OperationalPlanException,
275         GTUException
276     {
277         LanePerception perception = laneBasedGTU.getPerception();
278         GTUFollowingModelOld gfm = laneBasedGTU.getBehavioralCharacteristics().getGTUFollowingModel();
279 
280         // No lane change. Continue on current lane.
281         AccelerationStep accelerationStep;
282         if (perception.getForwardHeadwayGTU().getGtuId() == null)
283         {
284             accelerationStep =
285                 gfm.computeAccelerationStepWithNoLeader(laneBasedGTU,
286                     lanePathInfo.getPath().getLength().minus(laneBasedGTU.getLength().multiplyBy(2.0)),
287                     perception.getSpeedLimit());
288         }
289         else
290         {
291             accelerationStep =
292                 gfm.computeAccelerationStep(laneBasedGTU, perception.getForwardHeadwayGTU().getGtuSpeed(), perception
293                     .getForwardHeadwayGTU().getDistance(),
294                     lanePathInfo.getPath().getLength().minus(laneBasedGTU.getLength().multiplyBy(2.0)), perception
295                         .getSpeedLimit());
296         }
297 
298         // see if we have to continue standing still. In that case, generate a stand still plan
299         if (accelerationStep.getAcceleration().si < 1E-6
300             && laneBasedGTU.getVelocity().si < OperationalPlan.DRIFTING_SPEED_SI)
301         {
302             return new OperationalPlan(laneBasedGTU, locationAtStartTime, startTime, accelerationStep.getDuration());
303         }
304 
305         // build a list of lanes forward, with a maximum headway.
306         List<Segment> operationalPlanSegmentList = new ArrayList<>();
307         if (accelerationStep.getAcceleration().si == 0.0)
308         {
309             Segment segment = new OperationalPlan.SpeedSegment(accelerationStep.getDuration());
310             operationalPlanSegmentList.add(segment);
311         }
312         else
313         {
314             Segment segment =
315                 new OperationalPlan.AccelerationSegment(accelerationStep.getDuration(),
316                     accelerationStep.getAcceleration());
317             operationalPlanSegmentList.add(segment);
318         }
319         OperationalPlan op =
320             new OperationalPlan(laneBasedGTU, lanePathInfo.getPath(), startTime, laneBasedGTU.getVelocity(),
321                 operationalPlanSegmentList);
322         return op;
323     }
324 
325     /**
326      * We are not on a lane that leads to our destination. Determine whether the lateral direction to go is left or right.
327      * @param laneBasedGTU the gtu
328      * @param nextSplitInfo the information about the next split
329      * @return the lateral direction to go, or null if this cannot be determined
330      */
331     private LateralDirectionality
332         determineLeftRight(final LaneBasedGTU laneBasedGTU, final NextSplitInfo nextSplitInfo)
333     {
334         // are the lanes in nextSplitInfo.getCorrectCurrentLanes() left or right of the current lane(s) of the GTU?
335         for (Lane correctLane : nextSplitInfo.getCorrectCurrentLanes())
336         {
337             for (Lane currentLane : laneBasedGTU.getLanes().keySet())
338             {
339                 if (correctLane.getParentLink().equals(currentLane.getParentLink()))
340                 {
341                     double deltaOffset =
342                         correctLane.getDesignLineOffsetAtBegin().si - currentLane.getDesignLineOffsetAtBegin().si;
343                     if (laneBasedGTU.getLanes().get(currentLane).equals(GTUDirectionality.DIR_PLUS))
344                     {
345                         return deltaOffset > 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
346                     }
347                     else
348                     {
349                         return deltaOffset < 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
350                     }
351                 }
352             }
353         }
354         return null;
355     }
356 
357     /**
358      * See if a lane change in the given direction if possible.
359      * @param gtu the GTU that has to make the lane change
360      * @param perception the perception, where forward headway, accessible lanes and speed limit have been assessed
361      * @param lanePathInfo the information for the path on the current lane
362      * @param direction the lateral direction, either LEFT or RIGHT
363      * @return whether a lane change is possible.
364      * @throws NetworkException when there is a network inconsistency in updating the perception
365      * @throws GTUException when there is an issue retrieving GTU information for the perception update
366      */
367     private boolean canChange(final LaneBasedGTU gtu, final LanePerception perception, final LanePathInfo lanePathInfo,
368         final LateralDirectionality direction) throws GTUException, NetworkException
369     {
370         Collection<HeadwayGTU> otherLaneTraffic;
371         perception.updateForwardHeadwayGTU();
372         perception.updateBackwardHeadwayGTU();
373         if (direction.isLeft())
374         {
375             perception.updateParallelGTUsLeft();
376             perception.updateLaneTrafficLeft();
377             otherLaneTraffic = perception.getNeighboringGTUsLeft();
378         }
379         else
380         {
381             perception.updateParallelGTUsRight();
382             perception.updateLaneTrafficRight();
383             otherLaneTraffic = perception.getNeighboringGTUsRight();
384         }
385         if (!perception.parallelGTUs(direction).isEmpty())
386         {
387             return false;
388         }
389 
390         Collection<HeadwayGTU> sameLaneTraffic = new HashSet<>();
391         if (perception.getForwardHeadwayGTU() != null && perception.getForwardHeadwayGTU().getGtuId() != null)
392         {
393             sameLaneTraffic.add(perception.getForwardHeadwayGTU());
394         }
395         if (perception.getBackwardHeadwayGTU() != null && perception.getBackwardHeadwayGTU().getGtuId() != null)
396         {
397             sameLaneTraffic.add(perception.getBackwardHeadwayGTU());
398         }
399 
400         // TODO make type of plan (Egoistic, Altruistic) parameter of the class
401         DirectedLaneChangeModel dlcm = new DirectedEgoistic();
402         // TODO make the elasticities 2.0 and 0.1 parameters of the class
403         DirectedLaneMovementStep dlms =
404             dlcm.computeLaneChangeAndAcceleration(gtu, direction, sameLaneTraffic, otherLaneTraffic, gtu
405                 .getBehavioralCharacteristics().getForwardHeadwayDistance(), perception.getSpeedLimit(), new Acceleration(
406                 2.0, AccelerationUnit.SI), new Acceleration(0.1, AccelerationUnit.SI), new Time.Rel(0.5,
407                 TimeUnit.SECOND));
408         if (dlms.getLaneChange() == null)
409         {
410             return false;
411         }
412 
413         return true;
414     }
415 
416     /**
417      * Change lanes instantaneously.
418      * @param gtu the gtu
419      * @param direction the direction
420      * @return the new location of the reference point of the GTU
421      * @throws GTUException in case the enterlane fails
422      */
423     private DirectedPoint changeLane(final LaneBasedGTU gtu, final LateralDirectionality direction) throws GTUException
424     {
425         DirectedPoint p = null;
426         Set<Lane> lanes = new HashSet<>(gtu.getLanes().keySet());
427         for (Lane lane : lanes)
428         {
429             Set<Lane> adjacentLanes = lane.accessibleAdjacentLanes(direction, gtu.getGTUType());
430             double fraction = gtu.fractionalPosition(lane, gtu.getReference());
431             for (Lane adjacentLane : adjacentLanes)
432             {
433                 gtu.enterLane(adjacentLane, adjacentLane.getLength().multiplyBy(fraction), gtu.getLanes().get(lane));
434                 if (fraction >= 0.0 && fraction <= 1.0)
435                 {
436                     p = adjacentLane.getCenterLine().getLocationFractionExtended(fraction);
437                     this.referenceLane = adjacentLane;
438                     this.referencePos = adjacentLane.getLength().multiplyBy(fraction);
439                 }
440                 System.out.println("gtu " + gtu.getId() + " entered lane " + adjacentLane + " at pos "
441                     + adjacentLane.getLength().multiplyBy(fraction));
442             }
443             gtu.leaveLane(lane);
444         }
445 
446         if (p == null)
447         {
448             System.err.println("Warning: " + gtu + " not with its reference point on any lane. Came from " + lanes
449                 + ", now at " + gtu.getLanes().keySet());
450             Lane l = gtu.getLanes().keySet().iterator().next();
451             double fraction = gtu.fractionalPosition(l, gtu.getReference());
452             p = l.getCenterLine().getLocationFractionExtended(fraction);
453             this.referenceLane = l;
454             this.referencePos = l.getLength().multiplyBy(fraction);
455         }
456 
457         // stay at least 15 seconds in the current lane
458         this.earliestNexLaneChangeTime =
459             gtu.getSimulator().getSimulatorTime().getTime().plus(new Time.Rel(15, TimeUnit.SECOND));
460 
461         // make sure out turn indicator is on!
462         gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
463         
464         return p;
465     }
466 
467     /**
468      * @return destroyGtuOnFailure, indicating when a failure in planning occurs, whether we should destroy the GTU to avoid
469      *         halting of the model
470      */
471     public final boolean isDestroyGtuOnFailure()
472     {
473         return this.destroyGtuOnFailure;
474     }
475 
476     /**
477      * When a failure in planning occurs, should we destroy the GTU to avoid halting of the model?
478      * @param destroyGtuOnFailure set destroyGtuOnFailure to true or false
479      */
480     public final void setDestroyGtuOnFailure(final boolean destroyGtuOnFailure)
481     {
482         this.destroyGtuOnFailure = destroyGtuOnFailure;
483     }
484 
485 }