View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical.util;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.HashMap;
6   import java.util.List;
7   import java.util.SortedMap;
8   import java.util.TreeMap;
9   import java.util.UUID;
10  
11  import org.djunits.unit.AccelerationUnit;
12  import org.djunits.unit.DurationUnit;
13  import org.djunits.unit.LengthUnit;
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.djutils.exceptions.Throw;
20  import org.opentrafficsim.base.parameters.ParameterException;
21  import org.opentrafficsim.base.parameters.ParameterTypeAcceleration;
22  import org.opentrafficsim.base.parameters.ParameterTypeDouble;
23  import org.opentrafficsim.base.parameters.ParameterTypeDuration;
24  import org.opentrafficsim.base.parameters.ParameterTypeLength;
25  import org.opentrafficsim.base.parameters.ParameterTypes;
26  import org.opentrafficsim.base.parameters.Parameters;
27  import org.opentrafficsim.base.parameters.constraint.ConstraintInterface;
28  import org.opentrafficsim.core.gtu.GTUException;
29  import org.opentrafficsim.core.gtu.GTUType;
30  import org.opentrafficsim.core.gtu.TurnIndicatorIntent;
31  import org.opentrafficsim.core.network.Node;
32  import org.opentrafficsim.core.network.route.Route;
33  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
34  import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
35  import org.opentrafficsim.road.gtu.lane.perception.PerceptionIterable;
36  import org.opentrafficsim.road.gtu.lane.perception.headway.AbstractHeadwayGTU;
37  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayConflict;
38  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
39  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTUSimple;
40  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayStopLine;
41  import org.opentrafficsim.road.gtu.lane.tactical.Blockable;
42  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
43  import org.opentrafficsim.road.gtu.lane.tactical.pt.BusSchedule;
44  import org.opentrafficsim.road.network.lane.CrossSectionLink;
45  import org.opentrafficsim.road.network.lane.conflict.BusStopConflictRule;
46  import org.opentrafficsim.road.network.lane.conflict.Conflict;
47  import org.opentrafficsim.road.network.lane.conflict.ConflictRule;
48  import org.opentrafficsim.road.network.lane.conflict.ConflictType;
49  import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
50  
51  /**
52   * This class implements default behavior for intersection conflicts for use in tactical planners.
53   * <p>
54   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
55   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
56   * <p>
57   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jun 3, 2016 <br>
58   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
59   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
60   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
61   */
62  // TODO do not ignore vehicles upstream of conflict if they have green
63  // TODO conflict over multiple lanes (longitudinal in own direction)
64  // TODO a) yielding while having priority happens only when leaders is standing still on conflict (then its useless...)
65  // b) two vehicles can remain upstream of merge if vehicle stands on merge but leaves some space to move
66  // probably 1 is yielding, and 1 is courtesy yielding as the other stands still
67  // c) they might start moving together and collide further down (no response to negative headway on merge)
68  public final class ConflictUtil
69  {
70  
71      /** Minimum time gap between events. */
72      public static final ParameterTypeDuration MIN_GAP = new ParameterTypeDuration("minGap", "Minimum gap for conflicts.",
73              new Duration(0.000001, DurationUnit.SECOND), ConstraintInterface.POSITIVE);
74  
75      /** Comfortable deceleration. */
76      public static final ParameterTypeAcceleration B = ParameterTypes.B;
77  
78      /** Critical deceleration. */
79      public static final ParameterTypeAcceleration BCRIT = ParameterTypes.BCRIT;
80  
81      /** Stopping distance. */
82      public static final ParameterTypeLength S0 = ParameterTypes.S0;
83  
84      /** Stopping distance at conflicts. */
85      public static final ParameterTypeLength S0_CONF = new ParameterTypeLength("s0 conf", "Stopping distance at conflicts.",
86              new Length(1.5, LengthUnit.METER), ConstraintInterface.POSITIVE);
87  
88      /** Multiplication factor on time for conservative assessment. */
89      public static final ParameterTypeDouble TIME_FACTOR =
90              new ParameterTypeDouble("timeFactor", "Safety factor on estimated time.", 1.25, ConstraintInterface.ATLEASTONE);
91  
92      /** Area before stop line where one is considered arrived at the intersection. */
93      public static final ParameterTypeLength STOP_AREA =
94              new ParameterTypeLength("stopArea", "Area before stop line where one is considered arrived at the intersection.",
95                      new Length(4, LengthUnit.METER), ConstraintInterface.POSITIVE);
96  
97      /** Parameter of how much time before departure a bus indicates its departure to get priority. */
98      public static final ParameterTypeDuration TI = new ParameterTypeDuration("ti", "Indicator time before departure.",
99              Duration.createSI(3.0), ConstraintInterface.POSITIVE);
100 
101     /** Time step for free acceleration anticipation. */
102     private static final Duration TIME_STEP = new Duration(0.5, DurationUnit.SI);
103 
104     /** Cross standing vehicles on crossings. We allow this to prevent dead-locks. A better model should render this useless. */
105     private static boolean CROSSSTANDING = true;
106 
107     /**
108      * Do not instantiate.
109      */
110     private ConflictUtil()
111     {
112         //
113     }
114 
115     /**
116      * Approach conflicts by applying appropriate acceleration (or deceleration). The model may yield for a vehicle even while
117      * having priority. Such a 'yield plan' is remembered in <tt>YieldPlans</tt>. By forwarding the same <tt>YieldPlans</tt> for
118      * a GTU consistency of such plans is provided. If any conflict is not accepted to pass, stopping before a more upstream
119      * conflict is applied if there not sufficient stopping length in between conflicts.
120      * @param parameters Parameters; parameters
121      * @param conflicts PerceptionCollectable&lt;HeadwayConflict,Conflict&gt;; set of conflicts to approach
122      * @param leaders PerceptionCollectable&lt;HeadwayGTU,LaneBasedGTU&gt;; leading vehicles
123      * @param carFollowingModel CarFollowingModel; car-following model
124      * @param vehicleLength Length; length of vehicle
125      * @param vehicleWidth Length; width of vehicle
126      * @param speed Speed; current speed
127      * @param acceleration Acceleration; current acceleration
128      * @param speedLimitInfo SpeedLimitInfo; speed limit info
129      * @param conflictPlans ConflictPlans; set of plans for conflict
130      * @param gtu LaneBasedGTU; gtu
131      * @return acceleration appropriate for approaching the conflicts
132      * @throws GTUException in case of an unsupported conflict rule
133      * @throws ParameterException if a parameter is not defined or out of bounds
134      */
135     @SuppressWarnings("checkstyle:parameternumber")
136     public static Acceleration approachConflicts(final Parameters parameters,
137             final PerceptionCollectable<HeadwayConflict, Conflict> conflicts,
138             final PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders, final CarFollowingModel carFollowingModel,
139             final Length vehicleLength, final Length vehicleWidth, final Speed speed, final Acceleration acceleration,
140             final SpeedLimitInfo speedLimitInfo, final ConflictPlans conflictPlans, final LaneBasedGTU gtu)
141             throws GTUException, ParameterException
142     {
143 
144         conflictPlans.cleanPlans();
145 
146         Acceleration a = Acceleration.POS_MAXVALUE;
147         Length stoppingDistance = Length.createSI(
148                 parameters.getParameter(S0).si + vehicleLength.si + .5 * speed.si * speed.si / parameters.getParameter(B).si);
149         if (!conflicts.isEmpty() && conflicts.first().getDistance().gt(stoppingDistance))
150         {
151             conflictPlans.setBlocking(false);
152             return a;
153         }
154 
155         List<Length> prevStarts = new ArrayList<>();
156         List<Length> prevEnds = new ArrayList<>();
157         List<Class<? extends ConflictRule>> conflictRuleTypes = new ArrayList<>();
158         boolean blocking = false;
159 
160         for (HeadwayConflict conflict : conflicts)
161         {
162 
163             // adjust acceleration for situations where stopping might not be required
164             if (conflict.isCrossing())
165             {
166                 // avoid collision if crossing is occupied
167                 a = Acceleration.min(a, avoidCrossingCollision(parameters, conflict, carFollowingModel, speed, speedLimitInfo));
168             }
169             else
170             {
171                 // follow leading GTUs on merge or split
172                 a = Acceleration.min(a, followConflictingLeaderOnMergeOrSplit(conflict, parameters, carFollowingModel, speed,
173                         speedLimitInfo, vehicleWidth));
174             }
175             if (conflict.getDistance().lt0())
176             {
177                 if (conflict.getConflictType().isCrossing() && !conflict.getConflictPriority().isPriority())
178                 {
179                     // note that we are blocking a conflict
180                     blocking = true;
181                 }
182                 // ignore conflicts we are on (i.e. negative distance to start of conflict)
183                 continue;
184             }
185 
186             // indicator if bus
187             if (gtu.getStrategicalPlanner().getRoute() instanceof BusSchedule && gtu.getGTUType().isOfType(GTUType.BUS)
188                     && conflict.getConflictRuleType().equals(BusStopConflictRule.class))
189             {
190                 BusSchedule busSchedule = (BusSchedule) gtu.getStrategicalPlanner().getRoute();
191                 Time actualDeparture = busSchedule.getActualDepartureConflict(conflict.getId());
192                 if (actualDeparture != null
193                         && actualDeparture.si < gtu.getSimulator().getSimulatorTime().si + parameters.getParameter(TI).si)
194                 {
195                     // TODO depending on left/right-hand traffic
196                     conflictPlans.setIndicatorIntent(TurnIndicatorIntent.LEFT, conflict.getDistance());
197                 }
198             }
199 
200             // determine if we need to stop
201             boolean stop;
202             switch (conflict.getConflictPriority())
203             {
204                 case PRIORITY:
205                 {
206                     stop = stopForPriorityConflict(conflict, leaders, speed, vehicleLength, parameters);
207                     break;
208                 }
209                 case YIELD: // TODO depending on rules, we may need to stop and not just yield
210                 case TURN_ON_RED:
211                 {
212                     stop = stopForGiveWayConflict(conflict, leaders, speed, acceleration, vehicleLength, parameters,
213                             speedLimitInfo, carFollowingModel, blocking ? BCRIT : B);
214                     break;
215                 }
216                 case STOP:
217                 {
218                     stop = stopForStopConflict(conflict, leaders, speed, acceleration, vehicleLength, parameters,
219                             speedLimitInfo, carFollowingModel, blocking ? BCRIT : B);
220                     break;
221                 }
222                 case ALL_STOP:
223                 {
224                     stop = stopForAllStopConflict(conflict, conflictPlans);
225                     break;
226                 }
227                 case SPLIT:
228                 {
229                     stop = false; // skipped anyway
230                     break;
231                 }
232                 default:
233                 {
234                     throw new GTUException("Unsupported conflict rule encountered while approaching conflicts.");
235                 }
236             }
237 
238             // stop if required, account for upstream conflicts to keep clear
239             if (!conflict.getConflictType().equals(ConflictType.SPLIT))
240             {
241 
242                 if (stop)
243                 {
244                     prevStarts.add(conflict.getDistance());
245                     conflictRuleTypes.add(conflict.getConflictRuleType());
246                     // stop for first conflict looking upstream of this blocked conflict that allows sufficient space
247                     int j = 0; // most upstream conflict if not in between conflicts
248                     for (int i = prevEnds.size() - 1; i >= 0; i--) // downstream to upstream
249                     {
250                         // note, at this point prevStarts contains one more conflict than prevEnds
251                         if (prevStarts.get(i + 1).minus(prevEnds.get(i)).gt(passableDistance(vehicleLength, parameters)))
252                         {
253                             j = i + 1;
254                             break;
255                         }
256                     }
257                     if (blocking && j == 0)
258                     {
259                         // we are blocking a conflict, let's not stop more upstream than the conflict that forces our stop
260                         j = prevStarts.size() - 1;
261                     }
262 
263                     // TODO
264                     // if this lowers our acceleration, we need to check if we are able to pass upstream conflicts still in time
265 
266                     // stop for j'th conflict, if deceleration is too strong, for next one
267                     parameters.setParameterResettable(S0, parameters.getParameter(S0_CONF));
268                     Acceleration aCF = new Acceleration(-Double.MAX_VALUE, AccelerationUnit.SI);
269                     while (aCF.si < -6.0 && j < prevStarts.size())
270                     {
271                         if (prevStarts.get(j).lt(parameters.getParameter(S0_CONF)))
272                         {
273                             // TODO what to do when we happen to be in the stopping distance? Stopping might be reasonable,
274                             // while car-following might give strong deceleration due to s < s0.
275                             aCF = Acceleration.max(aCF, new Acceleration(-6.0, AccelerationUnit.SI));
276                         }
277                         else
278                         {
279                             Acceleration aStop = CarFollowingUtil.stop(carFollowingModel, parameters, speed, speedLimitInfo,
280                                     prevStarts.get(j));
281                             if (conflictRuleTypes.get(j).equals(BusStopConflictRule.class)
282                                     && aStop.lt(parameters.getParameter(ParameterTypes.BCRIT).neg()))
283                             {
284                                 // as it may suddenly switch state, i.e. ignore like a yellow traffic light
285                                 aStop = Acceleration.POS_MAXVALUE;
286                             }
287                             aCF = Acceleration.max(aCF, aStop);
288                         }
289                         j++;
290                     }
291                     parameters.resetParameter(S0);
292                     a = Acceleration.min(a, aCF);
293                     break;
294                 }
295 
296                 // keep conflict clear (when stopping for another conflict), if there are conflicting vehicles
297                 if (!conflict.getUpstreamConflictingGTUs().isEmpty())
298                 {
299                     prevStarts.add(conflict.getDistance());
300                     conflictRuleTypes.add(conflict.getConflictRuleType());
301                     prevEnds.add(conflict.getDistance().plus(conflict.getLength()));
302                 }
303             }
304 
305         }
306         conflictPlans.setBlocking(blocking);
307 
308         if (a.si < -6.0 && speed.si > 5 / 3.6)
309         {
310             System.err.println("Deceleration from conflict util stronger than 6m/s^2.");
311             // return IGNORE;
312         }
313         return a;
314     }
315 
316     /**
317      * Determines acceleration for following conflicting vehicles <i>on</i> a merge or split conflict.
318      * @param conflict HeadwayConflict; merge or split conflict
319      * @param parameters Parameters; parameters
320      * @param carFollowingModel CarFollowingModel; car-following model
321      * @param speed Speed; current speed
322      * @param speedLimitInfo SpeedLimitInfo; speed limit info
323      * @param vehicleWidth Length; own width
324      * @return acceleration for following conflicting vehicles <i>on</i> a merge or split conflict
325      * @throws ParameterException if a parameter is not given or out of bounds
326      */
327     private static Acceleration followConflictingLeaderOnMergeOrSplit(final HeadwayConflict conflict,
328             final Parameters parameters, final CarFollowingModel carFollowingModel, final Speed speed,
329             final SpeedLimitInfo speedLimitInfo, final Length vehicleWidth) throws ParameterException
330     {
331         // ignore if no conflicting GTU's, or if first is downstream of conflict
332         PerceptionIterable<HeadwayGTU> downstreamGTUs = conflict.getDownstreamConflictingGTUs();
333         if (downstreamGTUs.isEmpty() || downstreamGTUs.first().isAhead())
334         {
335             return Acceleration.POS_MAXVALUE;
336         }
337         // get the most upstream GTU to consider
338         HeadwayGTU c = null;
339         Length virtualHeadway = null;
340         if (conflict.getDistance().gt0())
341         {
342             c = downstreamGTUs.first();
343             virtualHeadway = conflict.getDistance().plus(c.getOverlapRear());
344         }
345         else
346         {
347             for (HeadwayGTU con : downstreamGTUs)
348             {
349                 if (con.isAhead())
350                 {
351                     // conflict GTU completely downstream of conflict (i.e. regular car-following, ignore here)
352                     return Acceleration.POS_MAXVALUE;
353                 }
354                 // conflict GTU (partially) on the conflict
355                 // {@formatter:off}
356                 // ______________________________________________ 
357                 //   ___      virtual headway   |  ___  |
358                 //  |___|(-----------------------)|___|(vehicle from south, on lane from south)
359                 // _____________________________|_______|________
360                 //                              /       / 
361                 //                             /       /
362                 // {@formatter:on}
363                 virtualHeadway = conflict.getDistance().plus(con.getOverlapRear());
364                 if (virtualHeadway.gt0())
365                 {
366                     if (conflict.isSplit())
367                     {
368                         double conflictWidth = conflict.getWidthAtFraction(
369                                 (-conflict.getDistance().si + virtualHeadway.si) / conflict.getConflictingLength().si).si;
370                         double gtuWidth = con.getWidth().si + vehicleWidth.si;
371                         if (conflictWidth > gtuWidth)
372                         {
373                             continue;
374                         }
375                     }
376                     // found first downstream GTU on conflict
377                     c = con;
378                     break;
379                 }
380             }
381         }
382         if (c == null)
383         {
384             // conflict GTU downstream of start of conflict, but upstream of us
385             return Acceleration.POS_MAXVALUE;
386         }
387         // follow leader
388         SortedMap<Length, Speed> leaders = new TreeMap<>();
389         leaders.put(virtualHeadway, c.getSpeed());
390         Acceleration a = CarFollowingUtil.followSingleLeader(carFollowingModel, parameters, speed, speedLimitInfo,
391                 virtualHeadway, c.getSpeed());
392         // if conflicting GTU is partially upstream of the conflict and at (near) stand-still, stop for the conflict rather than
393         // following the tail of the conflicting GTU
394         if (conflict.isMerge() && virtualHeadway.lt(conflict.getDistance()))
395         {
396             // {@formatter:off}
397             /*
398              * ______________________________________________
399              *    ___    stop for conflict  |       | 
400              *   |___|(--------------------)|   ___ |
401              * _____________________________|__/  /_|________ 
402              *                              / /__/  /
403              *                             /       /
404              */
405             // {@formatter:on}
406             parameters.setParameterResettable(S0, parameters.getParameter(S0_CONF));
407             Acceleration aStop =
408                     CarFollowingUtil.stop(carFollowingModel, parameters, speed, speedLimitInfo, conflict.getDistance());
409             parameters.resetParameter(S0);
410             a = Acceleration.max(a, aStop); // max, which ever allows the largest acceleration
411         }
412         return a;
413     }
414 
415     /**
416      * Determines an acceleration required to avoid a collision with GTUs <i>on</i> a crossing conflict.
417      * @param parameters Parameters; parameters
418      * @param conflict HeadwayConflict; conflict
419      * @param carFollowingModel CarFollowingModel; car-following model
420      * @param speed Speed; current speed
421      * @param speedLimitInfo SpeedLimitInfo; speed limit info
422      * @return acceleration required to avoid a collision
423      * @throws ParameterException if parameter is not defined
424      */
425     private static Acceleration avoidCrossingCollision(final Parameters parameters, final HeadwayConflict conflict,
426             final CarFollowingModel carFollowingModel, final Speed speed, final SpeedLimitInfo speedLimitInfo)
427             throws ParameterException
428     {
429 
430         // TODO only within visibility
431         List<HeadwayGTU> conflictingGTUs = new ArrayList<>();
432         for (HeadwayGTU gtu : conflict.getUpstreamConflictingGTUs())
433         {
434             if (isOnRoute(conflict.getConflictingLink(), gtu))
435             {
436                 // first upstream vehicle on route to this conflict
437                 conflictingGTUs.add(gtu);
438                 break;
439             }
440         }
441         for (HeadwayGTU gtu : conflict.getDownstreamConflictingGTUs())
442         {
443             if (gtu.isParallel())
444             {
445                 conflictingGTUs.add(gtu);
446             }
447             else
448             {
449                 // vehicles beyond conflict are not a thread
450                 break;
451             }
452         }
453 
454         if (conflictingGTUs.isEmpty())
455         {
456             return Acceleration.POS_MAXVALUE;
457         }
458 
459         Acceleration a = Acceleration.POS_MAXVALUE;
460         for (HeadwayGTU conflictingGTU : conflictingGTUs)
461         {
462             AnticipationInfo tteC;
463             Length distance;
464             if (conflictingGTU.isParallel())
465             {
466                 tteC = new AnticipationInfo(Duration.ZERO, conflictingGTU.getSpeed());
467                 distance = conflictingGTU.getOverlapRear().abs().plus(conflictingGTU.getOverlap())
468                         .plus(conflictingGTU.getOverlapFront().abs());
469             }
470             else
471             {
472                 tteC = AnticipationInfo.anticipateMovement(conflictingGTU.getDistance(), conflictingGTU.getSpeed(),
473                         Acceleration.ZERO);
474                 distance = conflictingGTU.getDistance().plus(conflict.getLength()).plus(conflictingGTU.getLength());
475             }
476             AnticipationInfo ttcC = AnticipationInfo.anticipateMovement(distance, conflictingGTU.getSpeed(), Acceleration.ZERO);
477             AnticipationInfo tteO = AnticipationInfo.anticipateMovementFreeAcceleration(conflict.getDistance(), speed,
478                     parameters, carFollowingModel, speedLimitInfo, TIME_STEP);
479             // enter before cleared
480             // TODO safety factor?
481             if (tteC.getDuration().lt(tteO.getDuration()) && tteO.getDuration().lt(ttcC.getDuration()))
482             {
483                 if (!conflictingGTU.getSpeed().eq0() || !CROSSSTANDING)
484                 {
485                     // solve parabolic speed profile s = v*t + .5*a*t*t, a =
486                     double acc = 2 * (conflict.getDistance().si - speed.si * ttcC.getDuration().si)
487                             / (ttcC.getDuration().si * ttcC.getDuration().si);
488                     // time till zero speed > time to avoid conflict?
489                     if (speed.si / -acc > ttcC.getDuration().si)
490                     {
491                         a = Acceleration.min(a, new Acceleration(acc, AccelerationUnit.SI));
492                     }
493                     else
494                     {
495                         // will reach zero speed ourselves
496                         a = Acceleration.min(a, CarFollowingUtil.stop(carFollowingModel, parameters, speed, speedLimitInfo,
497                                 conflict.getDistance()));
498                     }
499                 }
500                 // conflicting vehicle stand-still, ignore even at conflict
501             }
502         }
503         return a;
504     }
505 
506     /**
507      * Approach a priority conflict. Stopping is applied to give way to conflicting traffic in case congestion is present on the
508      * own lane. This is courtesy yielding.
509      * @param conflict HeadwayConflict; conflict to approach
510      * @param leaders PerceptionCollectable&lt;HeadwayGTU,LaneBasedGTU&gt;; leading vehicles in own lane
511      * @param speed Speed; current speed
512      * @param vehicleLength Length; vehicle length
513      * @param parameters Parameters; parameters
514      * @return whether to stop for this conflict
515      * @throws ParameterException if parameter B is not defined
516      */
517     public static boolean stopForPriorityConflict(final HeadwayConflict conflict,
518             final PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders, final Speed speed, final Length vehicleLength,
519             final Parameters parameters) throws ParameterException
520     {
521 
522         if (leaders.isEmpty() || conflict.getUpstreamConflictingGTUs().isEmpty())
523         {
524             // no leader, or no conflicting vehicle
525             return false;
526         }
527 
528         // Stop as long as some leader is standing still, and leader is not leaving sufficient space yet
529         // use start of conflict on merge, end of conflict on crossing
530         Length typeCorrection = conflict.isCrossing() ? conflict.getLength() : Length.ZERO;
531         // distance leader has to cover before we can pass the conflict
532         Length distance = conflict.getDistance().minus(leaders.first().getDistance())
533                 .plus(passableDistance(vehicleLength, parameters)).plus(typeCorrection);
534         if (distance.gt0())
535         {
536             // for ourselves
537             Length required = conflict.getDistance().plus(typeCorrection).plus(passableDistance(vehicleLength, parameters));
538             for (HeadwayGTU leader : leaders)
539             {
540                 if (leader.getSpeed().eq0())
541                 {
542                     // first stand-still leader is not fully upstream of the conflict (in that case, ignore), and does not
543                     // allow sufficient space for all vehicles in between
544                     return leader.getDistance().ge(conflict.getDistance()) && required.ge(leader.getDistance());
545                 }
546                 required = required // add required distance for leaders
547                         .plus(passableDistance(leader.getLength(), leader.getParameters()));
548             }
549         }
550         return false;
551 
552     }
553 
554     /**
555      * Approach a give-way conflict.
556      * @param conflict HeadwayConflict; conflict
557      * @param leaders PerceptionCollectable&lt;HeadwayGTU,LaneBasedGTU&gt;; leaders
558      * @param speed Speed; current speed
559      * @param acceleration Acceleration; current acceleration
560      * @param vehicleLength Length; vehicle length
561      * @param parameters Parameters; parameters
562      * @param speedLimitInfo SpeedLimitInfo; speed limit info
563      * @param carFollowingModel CarFollowingModel; car-following model
564      * @param bType ParameterTypeAcceleration; parameter type for considered deceleration
565      * @return whether to stop for this conflict
566      * @throws ParameterException if a parameter is not defined
567      */
568     @SuppressWarnings("checkstyle:parameternumber")
569     public static boolean stopForGiveWayConflict(final HeadwayConflict conflict,
570             final PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders, final Speed speed, final Acceleration acceleration,
571             final Length vehicleLength, final Parameters parameters, final SpeedLimitInfo speedLimitInfo,
572             final CarFollowingModel carFollowingModel, final ParameterTypeAcceleration bType) throws ParameterException
573     {
574 
575         // TODO conflicting vehicle on crossing conflict, but will leave sooner then we enter, so no problem?
576         // TODO more generally, also upstream conflicting vehicles at crossings may leave the conflict before we enter
577         if (conflict.getConflictType().equals(ConflictType.CROSSING) && !conflict.getDownstreamConflictingGTUs().isEmpty()
578                 && conflict.getDownstreamConflictingGTUs().first().isParallel())
579         {
580             // vehicle on the conflict
581             return true;
582         }
583 
584         // Get data independent of conflicting vehicle
585         // parameters
586         Acceleration b = parameters.getParameter(bType).neg();
587         double f = parameters.getParameter(TIME_FACTOR);
588         Duration gap = parameters.getParameter(MIN_GAP);
589         // time till conflict is cleared
590         Length distance = conflict.getDistance().plus(vehicleLength);
591         if (conflict.isCrossing())
592         {
593             // merge is cleared at start, crossing at end
594             distance = distance.plus(conflict.getLength());
595         }
596         // based on acceleration, limited by free acceleration
597         AnticipationInfo ttcOa = AnticipationInfo.anticipateMovementFreeAcceleration(distance, speed, parameters,
598                 carFollowingModel, speedLimitInfo, TIME_STEP);
599         // time till downstream vehicle will make the conflict passible, under constant speed or safe deceleration
600         AnticipationInfo ttpDz = null;
601         AnticipationInfo ttpDs = null;
602         if (conflict.isCrossing())
603         {
604             if (!leaders.isEmpty())
605             {
606                 distance = conflict.getDistance().minus(leaders.first().getDistance()).plus(conflict.getLength())
607                         .plus(passableDistance(vehicleLength, parameters));
608                 ttpDz = AnticipationInfo.anticipateMovement(distance, leaders.first().getSpeed(), Acceleration.ZERO);
609                 ttpDs = AnticipationInfo.anticipateMovement(distance, leaders.first().getSpeed(), b);
610             }
611             else
612             {
613                 // no leader so conflict is passible within a duration of 0
614                 ttpDz = new AnticipationInfo(Duration.ZERO, Speed.ZERO);
615                 ttpDs = new AnticipationInfo(Duration.ZERO, Speed.ZERO);
616             }
617         }
618 
619         PerceptionCollectable<HeadwayGTU, LaneBasedGTU> conflictingVehiclesCollectable = conflict.getUpstreamConflictingGTUs();
620         Iterable<HeadwayGTU> conflictingVehicles;
621         if (conflictingVehiclesCollectable.isEmpty())
622         {
623             if (conflict.getConflictingTrafficLightDistance() == null)
624             {
625                 // none within visibility, assume a conflicting vehicle just outside of visibility driving at speed limit
626                 try
627                 {
628                     HeadwayGTUSimple conflictGtu = new HeadwayGTUSimple("virtual " + UUID.randomUUID().toString(), GTUType.CAR,
629                             conflict.getConflictingVisibility(), new Length(4.0, LengthUnit.SI), new Length(2.0, LengthUnit.SI),
630                             conflict.getConflictingSpeedLimit(), Acceleration.ZERO, Speed.ZERO);
631                     List<HeadwayGTU> conflictingVehiclesList = new ArrayList<>();
632                     conflictingVehiclesList.add(conflictGtu);
633                     conflictingVehicles = conflictingVehiclesList;
634                 }
635                 catch (GTUException exception)
636                 {
637                     throw new RuntimeException("Could not create a virtual conflicting vehicle at visibility range.",
638                             exception);
639                 }
640             }
641             else
642             {
643                 // no conflicting vehicles
644                 return false;
645             }
646         }
647         else
648         {
649             conflictingVehicles = conflictingVehiclesCollectable;
650         }
651 
652         // Loop over conflicting vehicles
653         boolean first = true;
654         for (HeadwayGTU conflictingVehicle : conflictingVehicles)
655         {
656 
657             // do not stop if conflicting vehicle is standing still
658             if (first && conflictingVehicle.getSpeed().eq0() && conflictingVehicle.isAhead())
659             {
660                 return false;
661             }
662             first = false;
663 
664             // skip if not on route
665             if (!isOnRoute(conflict.getConflictingLink(), conflictingVehicle))
666             {
667                 continue;
668             }
669 
670             // time till conflict vehicle will enter, under free acceleration and safe deceleration
671             AnticipationInfo tteCa;
672             if (conflictingVehicle instanceof HeadwayGTUSimple)
673             {
674                 tteCa = AnticipationInfo.anticipateMovement(conflictingVehicle.getDistance(), conflictingVehicle.getSpeed(),
675                         conflictingVehicle.getAcceleration());
676             }
677             else
678             {
679                 Parameters params = conflictingVehicle.getParameters();
680                 SpeedLimitInfo sli = conflictingVehicle.getSpeedLimitInfo();
681                 CarFollowingModel cfm = conflictingVehicle.getCarFollowingModel();
682                 // Constant acceleration creates inf at stand still, triggering passing trough a congested stream
683                 tteCa = AnticipationInfo.anticipateMovementFreeAcceleration(conflictingVehicle.getDistance(),
684                         conflictingVehicle.getSpeed(), params, cfm, sli, TIME_STEP);
685             }
686             AnticipationInfo tteCs =
687                     AnticipationInfo.anticipateMovement(conflictingVehicle.getDistance(), conflictingVehicle.getSpeed(), b);
688 
689             // check gap
690             if (conflict.isMerge())
691             {
692 
693                 // Merge, will be each others followers, add time to overcome speed difference
694                 double vSelf = ttcOa.getEndSpeed().si;
695                 double speedDiff = conflictingVehicle.getSpeed().si - vSelf;
696                 speedDiff = speedDiff > 0 ? speedDiff : 0;
697                 Duration additionalTime = new Duration(speedDiff / -b.si, DurationUnit.SI);
698                 // check if conflict vehicle will be upstream after that time, position beyond conflict after additional time
699                 double followerFront = conflictingVehicle.getSpeed().si * ttcOa.getDuration().si
700                         - conflictingVehicle.getDistance().si + (conflictingVehicle.getSpeed().si * additionalTime.si
701                                 + 0.5 * b.si * additionalTime.si * additionalTime.si);
702                 double ownRear = vSelf * additionalTime.si; // constant speed after clearing
703                 Duration tMax = parameters.getParameter(ParameterTypes.TMAX);
704                 Length s0 = parameters.getParameter(S0);
705                 // 1) will clear the conflict after the conflict vehicle enters
706                 // 2) not sufficient time to overcome speed difference
707                 // 3) conflict vehicle will be too near after adjusting speed
708                 if (ttcOa.getDuration().multiplyBy(f).plus(gap).gt(tteCa.getDuration())
709                         || ttcOa.getDuration().plus(additionalTime).multiplyBy(f).plus(gap).gt(tteCs.getDuration())
710                         || ownRear < (followerFront + (tMax.si + gap.si) * vSelf + s0.si) * f)
711                 {
712                     return true;
713                 }
714 
715             }
716             else if (conflict.isCrossing())
717             {
718 
719                 // Crossing, stop if order of events is not ok
720                 // Should go before the conflict vehicle
721                 // 1) downstream vehicle must supply sufficient space before conflict vehicle will enter
722                 // 2) must clear the conflict before the conflict vehicle will enter
723                 // 3) if leader decelerates with b, conflict vehicle should be able to safely delay entering conflict
724                 if (ttpDz.getDuration().multiplyBy(f).plus(gap).gt(tteCa.getDuration())
725                         || ttcOa.getDuration().multiplyBy(f).plus(gap).gt(tteCa.getDuration())
726                         || ttpDs.getDuration().multiplyBy(f).plus(gap).gt(tteCs.getDuration()))
727                 {
728                     return true;
729                 }
730 
731             }
732             else
733             {
734                 throw new RuntimeException(
735                         "Conflict is of unknown type " + conflict.getConflictType() + ", which is not merge nor a crossing.");
736             }
737         }
738 
739         // No conflict vehicle triggered stopping
740         return false;
741 
742     }
743 
744     /**
745      * Approach a stop conflict. Currently this is equal to approaching a give-way conflict.
746      * @param conflict HeadwayConflict; conflict
747      * @param leaders PerceptionCollectable&lt;HeadwayGTU,LaneBasedGTU&gt;; leaders
748      * @param speed Speed; current speed
749      * @param acceleration Acceleration; current acceleration
750      * @param vehicleLength Length; vehicle length
751      * @param parameters Parameters; parameters
752      * @param speedLimitInfo SpeedLimitInfo; speed limit info
753      * @param carFollowingModel CarFollowingModel; car-following model
754      * @param bType ParameterTypeAcceleration; parameter type for considered deceleration
755      * @return whether to stop for this conflict
756      * @throws ParameterException if a parameter is not defined
757      */
758     @SuppressWarnings("checkstyle:parameternumber")
759     public static boolean stopForStopConflict(final HeadwayConflict conflict,
760             final PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders, final Speed speed, final Acceleration acceleration,
761             final Length vehicleLength, final Parameters parameters, final SpeedLimitInfo speedLimitInfo,
762             final CarFollowingModel carFollowingModel, final ParameterTypeAcceleration bType) throws ParameterException
763     {
764         // TODO stopping
765         return stopForGiveWayConflict(conflict, leaders, speed, acceleration, vehicleLength, parameters, speedLimitInfo,
766                 carFollowingModel, bType);
767     }
768 
769     /**
770      * Approach an all-stop conflict.
771      * @param conflict HeadwayConflict; conflict to approach
772      * @param conflictPlans ConflictPlans; set of plans for conflict
773      * @return whether to stop for this conflict
774      */
775     public static boolean stopForAllStopConflict(final HeadwayConflict conflict, final ConflictPlans conflictPlans)
776     {
777         // TODO all-stop behavior
778 
779         if (conflictPlans.isStopPhaseRun(conflict.getStopLine()))
780         {
781             return false;
782         }
783 
784         return false;
785     }
786 
787     /**
788      * Returns whether the conflicting link is on the route of the given gtu.
789      * @param conflictingLink CrossSectionLink; conflicting link
790      * @param gtu HeadwayGTU; gtu
791      * @return whether the conflict is on the route of the given gtu
792      */
793     private static boolean isOnRoute(final CrossSectionLink conflictingLink, final HeadwayGTU gtu)
794     {
795         try
796         {
797             Route route = gtu.getRoute();
798             if (route == null)
799             {
800                 // conservative assumption: it's on the route (gtu should be upstream of the conflict)
801                 return true;
802             }
803             Node startNode = conflictingLink.getStartNode();
804             Node endNode = conflictingLink.getEndNode();
805             return route.contains(startNode) && route.contains(endNode)
806                     && Math.abs(route.indexOf(endNode) - route.indexOf(startNode)) == 1;
807         }
808         catch (UnsupportedOperationException uoe)
809         {
810             // conservative assumption: it's on the route (gtu should be upstream of the conflict)
811             return true;
812         }
813     }
814 
815     /**
816      * Returns a speed dependent distance needed behind the leader to completely pass the conflict.
817      * @param vehicleLength Length; vehicle length
818      * @param parameters Parameters; parameters
819      * @return speed dependent distance needed behind the leader to completely pass the conflict
820      * @throws ParameterException if parameter is not available
821      */
822     private static Length passableDistance(final Length vehicleLength, final Parameters parameters) throws ParameterException
823     {
824         return parameters.getParameter(S0).plus(vehicleLength);
825     }
826 
827     /**
828      * Holds the tactical plans of a driver considering conflicts. These are remembered for consistency. For instance, if the
829      * decision is made to yield as current deceleration suggests it's safe to do so, but the trajectory for stopping in front
830      * of the conflict results in deceleration slightly above what is considered safe deceleration, the plan should not be
831      * abandoned. Decelerations above what is considered safe deceleration may result due to numerical overshoot or other factor
832      * coming into play in car-following models. Many other examples exist where a driver sticks to a certain plan.
833      * <p>
834      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
835      * <br>
836      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
837      * <p>
838      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jun 7, 2016 <br>
839      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
840      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
841      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
842      */
843     public static final class ConflictPlans implements Blockable, Serializable
844     {
845 
846         /** */
847         private static final long serialVersionUID = 20160811L;
848 
849         /** Phases of navigating an all-stop intersection per intersection. */
850         private final HashMap<String, StopPhase> stopPhases = new HashMap<>();
851 
852         /** Estimated arrival times of vehicles at all-stop intersection. */
853         private final HashMap<String, Time> arrivalTimes = new HashMap<>();
854 
855         /** Indicator intent. */
856         private TurnIndicatorIntent indicatorIntent = TurnIndicatorIntent.NONE;
857 
858         /** Distance to object causing turn indicator intent. */
859         private Length indicatorObjectDistance = null;
860 
861         /** Whether the GTU is blocking conflicts. */
862         private boolean blocking;
863 
864         /**
865          * Clean any yield plan that was no longer kept active in the last evaluation of conflicts.
866          */
867         void cleanPlans()
868         {
869             this.indicatorIntent = TurnIndicatorIntent.NONE;
870             this.indicatorObjectDistance = null;
871         }
872 
873         /**
874          * Sets the estimated arrival time of a GTU.
875          * @param gtu AbstractHeadwayGTU; GTU
876          * @param time Time; estimated arrival time
877          */
878         void setArrivalTime(final AbstractHeadwayGTU gtu, final Time time)
879         {
880             this.arrivalTimes.put(gtu.getId(), time);
881         }
882 
883         /**
884          * Returns the estimated arrival time of given GTU.
885          * @param gtu AbstractHeadwayGTU; GTU
886          * @return estimated arrival time of given GTU
887          */
888         Time getArrivalTime(final AbstractHeadwayGTU gtu)
889         {
890             return this.arrivalTimes.get(gtu.getId());
891         }
892 
893         /**
894          * Sets the current phase to 'approach' for the given stop line.
895          * @param stopLine HeadwayStopLine; stop line
896          */
897         void setStopPhaseApproach(final HeadwayStopLine stopLine)
898         {
899             this.stopPhases.put(stopLine.getId(), StopPhase.APPROACH);
900         }
901 
902         /**
903          * Sets the current phase to 'yield' for the given stop line.
904          * @param stopLine HeadwayStopLine; stop line
905          * @throws RuntimeException if the phase was not set to approach before
906          */
907         void setStopPhaseYield(final HeadwayStopLine stopLine)
908         {
909             Throw.when(
910                     !this.stopPhases.containsKey(stopLine.getId())
911                             || !this.stopPhases.get(stopLine.getId()).equals(StopPhase.APPROACH),
912                     RuntimeException.class, "Yield stop phase is set for stop line that was not approached.");
913             this.stopPhases.put(stopLine.getId(), StopPhase.YIELD);
914         }
915 
916         /**
917          * Sets the current phase to 'run' for the given stop line.
918          * @param stopLine HeadwayStopLine; stop line
919          * @throws RuntimeException if the phase was not set to approach before
920          */
921         void setStopPhaseRun(final HeadwayStopLine stopLine)
922         {
923             Throw.when(!this.stopPhases.containsKey(stopLine.getId()), RuntimeException.class,
924                     "Run stop phase is set for stop line that was not approached.");
925             this.stopPhases.put(stopLine.getId(), StopPhase.YIELD);
926         }
927 
928         /**
929          * @param stopLine HeadwayStopLine; stop line
930          * @return whether the current phase is 'approach' for the given stop line
931          */
932         boolean isStopPhaseApproach(final HeadwayStopLine stopLine)
933         {
934             return this.stopPhases.containsKey(stopLine.getId())
935                     && this.stopPhases.get(stopLine.getId()).equals(StopPhase.APPROACH);
936         }
937 
938         /**
939          * @param stopLine HeadwayStopLine; stop line
940          * @return whether the current phase is 'yield' for the given stop line
941          */
942         boolean isStopPhaseYield(final HeadwayStopLine stopLine)
943         {
944             return this.stopPhases.containsKey(stopLine.getId())
945                     && this.stopPhases.get(stopLine.getId()).equals(StopPhase.YIELD);
946         }
947 
948         /**
949          * @param stopLine HeadwayStopLine; stop line
950          * @return whether the current phase is 'run' for the given stop line
951          */
952         boolean isStopPhaseRun(final HeadwayStopLine stopLine)
953         {
954             return this.stopPhases.containsKey(stopLine.getId()) && this.stopPhases.get(stopLine.getId()).equals(StopPhase.RUN);
955         }
956 
957         /** {@inheritDoc} */
958         @Override
959         public String toString()
960         {
961             return "ConflictPlans";
962         }
963 
964         /**
965          * @return indicatorIntent.
966          */
967         public TurnIndicatorIntent getIndicatorIntent()
968         {
969             return this.indicatorIntent;
970         }
971 
972         /**
973          * @return indicatorObjectDistance.
974          */
975         public Length getIndicatorObjectDistance()
976         {
977             return this.indicatorObjectDistance;
978         }
979 
980         /**
981          * @param intent TurnIndicatorIntent; indicator intent
982          * @param distance Length; distance to object pertaining to the turn indicator intent
983          */
984         public void setIndicatorIntent(final TurnIndicatorIntent intent, final Length distance)
985         {
986             if (this.indicatorObjectDistance == null || this.indicatorObjectDistance.gt(distance))
987             {
988                 this.indicatorIntent = intent;
989                 this.indicatorObjectDistance = distance;
990             }
991         }
992 
993         /** {@inheritDoc} */
994         @Override
995         public boolean isBlocking()
996         {
997             return this.blocking;
998         }
999 
1000         /**
1001          * Sets the GTU as blocking conflicts or not.
1002          * @param blocking boolean; whether the GTU is blocking conflicts
1003          */
1004         public void setBlocking(final boolean blocking)
1005         {
1006             this.blocking = blocking;
1007         }
1008 
1009     }
1010 
1011     /**
1012      * Phases of navigating an all-stop intersection.
1013      * <p>
1014      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1015      * <br>
1016      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
1017      * <p>
1018      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jun 30, 2016 <br>
1019      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
1020      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
1021      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
1022      */
1023     private enum StopPhase
1024     {
1025         /** Approaching stop intersection. */
1026         APPROACH,
1027 
1028         /** Yielding for stop intersection. */
1029         YIELD,
1030 
1031         /** Running over stop intersection. */
1032         RUN;
1033     }
1034 
1035 }