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, conflictPlans);
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      * @param yieldPlans ConflictPlans; set of plans for yielding with priority
515      * @return whether to stop for this conflict
516      * @throws ParameterException if parameter B is not defined
517      */
518     public static boolean stopForPriorityConflict(final HeadwayConflict conflict,
519             final PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders, final Speed speed, final Length vehicleLength,
520             final Parameters parameters, final ConflictPlans yieldPlans) throws ParameterException
521     {
522 
523         if (leaders.isEmpty() || conflict.getUpstreamConflictingGTUs().isEmpty())
524         {
525             // no leader, or no conflicting vehicle
526             return false;
527         }
528 
529         // Stop as long as some leader is standing still, and leader is not leaving sufficient space yet
530         // use start of conflict on merge, end of conflict on crossing
531         Length typeCorrection = conflict.isCrossing() ? conflict.getLength() : Length.ZERO;
532         // distance leader has to cover before we can pass the conflict
533         Length distance = conflict.getDistance().minus(leaders.first().getDistance())
534                 .plus(passableDistance(vehicleLength, parameters)).plus(typeCorrection);
535         if (distance.gt0())
536         {
537             // for ourselves
538             Length required = conflict.getDistance().plus(typeCorrection).plus(passableDistance(vehicleLength, parameters));
539             for (HeadwayGTU leader : leaders)
540             {
541                 if (leader.getSpeed().eq0())
542                 {
543                     // first stand-still leader is not fully upstream of the conflict (in that case, ignore), and does not
544                     // allow sufficient space for all vehicles in between
545                     return leader.getDistance().ge(conflict.getDistance()) && required.ge(leader.getDistance());
546                 }
547                 required = required // add required distance for leaders
548                         .plus(passableDistance(leader.getLength(), leader.getParameters()));
549             }
550         }
551         return false;
552 
553     }
554 
555     /**
556      * Approach a give-way conflict.
557      * @param conflict HeadwayConflict; conflict
558      * @param leaders PerceptionCollectable&lt;HeadwayGTU,LaneBasedGTU&gt;; leaders
559      * @param speed Speed; current speed
560      * @param acceleration Acceleration; current acceleration
561      * @param vehicleLength Length; vehicle length
562      * @param parameters Parameters; parameters
563      * @param speedLimitInfo SpeedLimitInfo; speed limit info
564      * @param carFollowingModel CarFollowingModel; car-following model
565      * @param bType ParameterTypeAcceleration; parameter type for considered deceleration
566      * @return whether to stop for this conflict
567      * @throws ParameterException if a parameter is not defined
568      */
569     @SuppressWarnings("checkstyle:parameternumber")
570     public static boolean stopForGiveWayConflict(final HeadwayConflict conflict,
571             final PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders, final Speed speed, final Acceleration acceleration,
572             final Length vehicleLength, final Parameters parameters, final SpeedLimitInfo speedLimitInfo,
573             final CarFollowingModel carFollowingModel, final ParameterTypeAcceleration bType) throws ParameterException
574     {
575 
576         // TODO conflicting vehicle on crossing conflict, but will leave sooner then we enter, so no problem?
577         // TODO more generally, also upstream conflicting vehicles at crossings may leave the conflict before we enter
578         if (conflict.getConflictType().equals(ConflictType.CROSSING) && !conflict.getDownstreamConflictingGTUs().isEmpty()
579                 && conflict.getDownstreamConflictingGTUs().first().isParallel())
580         {
581             // vehicle on the conflict
582             return true;
583         }
584 
585         // Get data independent of conflicting vehicle
586         // parameters
587         Acceleration b = parameters.getParameter(bType).neg();
588         double f = parameters.getParameter(TIME_FACTOR);
589         Duration gap = parameters.getParameter(MIN_GAP);
590         // time till conflict is cleared
591         Length distance = conflict.getDistance().plus(vehicleLength);
592         if (conflict.isCrossing())
593         {
594             // merge is cleared at start, crossing at end
595             distance = distance.plus(conflict.getLength());
596         }
597         // based on acceleration, limited by free acceleration
598         AnticipationInfo ttcOa = AnticipationInfo.anticipateMovementFreeAcceleration(distance, speed, parameters,
599                 carFollowingModel, speedLimitInfo, TIME_STEP);
600         // time till downstream vehicle will make the conflict passible, under constant speed or safe deceleration
601         AnticipationInfo ttpDz = null;
602         AnticipationInfo ttpDs = null;
603         if (conflict.isCrossing())
604         {
605             if (!leaders.isEmpty())
606             {
607                 distance = conflict.getDistance().minus(leaders.first().getDistance()).plus(conflict.getLength())
608                         .plus(passableDistance(vehicleLength, parameters));
609                 ttpDz = AnticipationInfo.anticipateMovement(distance, leaders.first().getSpeed(), Acceleration.ZERO);
610                 ttpDs = AnticipationInfo.anticipateMovement(distance, leaders.first().getSpeed(), b);
611             }
612             else
613             {
614                 // no leader so conflict is passible within a duration of 0
615                 ttpDz = new AnticipationInfo(Duration.ZERO, Speed.ZERO);
616                 ttpDs = new AnticipationInfo(Duration.ZERO, Speed.ZERO);
617             }
618         }
619 
620         PerceptionCollectable<HeadwayGTU, LaneBasedGTU> conflictingVehiclesCollectable = conflict.getUpstreamConflictingGTUs();
621         Iterable<HeadwayGTU> conflictingVehicles;
622         if (conflictingVehiclesCollectable.isEmpty())
623         {
624             if (conflict.getConflictingTrafficLightDistance() == null)
625             {
626                 // none within visibility, assume a conflicting vehicle just outside of visibility driving at speed limit
627                 try
628                 {
629                     HeadwayGTUSimple conflictGtu = new HeadwayGTUSimple("virtual " + UUID.randomUUID().toString(), GTUType.CAR,
630                             conflict.getConflictingVisibility(), new Length(4.0, LengthUnit.SI), new Length(2.0, LengthUnit.SI),
631                             conflict.getConflictingSpeedLimit(), Acceleration.ZERO, Speed.ZERO);
632                     List<HeadwayGTU> conflictingVehiclesList = new ArrayList<>();
633                     conflictingVehiclesList.add(conflictGtu);
634                     conflictingVehicles = conflictingVehiclesList;
635                 }
636                 catch (GTUException exception)
637                 {
638                     throw new RuntimeException("Could not create a virtual conflicting vehicle at visibility range.",
639                             exception);
640                 }
641             }
642             else
643             {
644                 // no conflicting vehicles
645                 return false;
646             }
647         }
648         else
649         {
650             conflictingVehicles = conflictingVehiclesCollectable;
651         }
652 
653         // Loop over conflicting vehicles
654         boolean first = true;
655         for (HeadwayGTU conflictingVehicle : conflictingVehicles)
656         {
657 
658             // do not stop if conflicting vehicle is standing still
659             if (first && conflictingVehicle.getSpeed().eq0() && conflictingVehicle.isAhead())
660             {
661                 return false;
662             }
663             first = false;
664 
665             // skip if not on route
666             if (!isOnRoute(conflict.getConflictingLink(), conflictingVehicle))
667             {
668                 continue;
669             }
670 
671             // time till conflict vehicle will enter, under free acceleration and safe deceleration
672             AnticipationInfo tteCa;
673             if (conflictingVehicle instanceof HeadwayGTUSimple)
674             {
675                 tteCa = AnticipationInfo.anticipateMovement(conflictingVehicle.getDistance(), conflictingVehicle.getSpeed(),
676                         conflictingVehicle.getAcceleration());
677             }
678             else
679             {
680                 Parameters params = conflictingVehicle.getParameters();
681                 SpeedLimitInfo sli = conflictingVehicle.getSpeedLimitInfo();
682                 CarFollowingModel cfm = conflictingVehicle.getCarFollowingModel();
683                 // Constant acceleration creates inf at stand still, triggering passing trough a congested stream
684                 tteCa = AnticipationInfo.anticipateMovementFreeAcceleration(conflictingVehicle.getDistance(),
685                         conflictingVehicle.getSpeed(), params, cfm, sli, TIME_STEP);
686             }
687             AnticipationInfo tteCs =
688                     AnticipationInfo.anticipateMovement(conflictingVehicle.getDistance(), conflictingVehicle.getSpeed(), b);
689 
690             // check gap
691             if (conflict.isMerge())
692             {
693 
694                 // Merge, will be each others followers, add time to overcome speed difference
695                 double vSelf = ttcOa.getEndSpeed().si;
696                 double speedDiff = conflictingVehicle.getSpeed().si - vSelf;
697                 speedDiff = speedDiff > 0 ? speedDiff : 0;
698                 Duration additionalTime = new Duration(speedDiff / -b.si, DurationUnit.SI);
699                 // check if conflict vehicle will be upstream after that time, position beyond conflict after additional time
700                 double followerFront = conflictingVehicle.getSpeed().si * ttcOa.getDuration().si
701                         - conflictingVehicle.getDistance().si + (conflictingVehicle.getSpeed().si * additionalTime.si
702                                 + 0.5 * b.si * additionalTime.si * additionalTime.si);
703                 double ownRear = vSelf * additionalTime.si; // constant speed after clearing
704                 Duration tMax = parameters.getParameter(ParameterTypes.TMAX);
705                 Length s0 = parameters.getParameter(S0);
706                 // 1) will clear the conflict after the conflict vehicle enters
707                 // 2) not sufficient time to overcome speed difference
708                 // 3) conflict vehicle will be too near after adjusting speed
709                 if (ttcOa.getDuration().multiplyBy(f).plus(gap).gt(tteCa.getDuration())
710                         || ttcOa.getDuration().plus(additionalTime).multiplyBy(f).plus(gap).gt(tteCs.getDuration())
711                         || ownRear < (followerFront + (tMax.si + gap.si) * vSelf + s0.si) * f)
712                 {
713                     return true;
714                 }
715 
716             }
717             else if (conflict.isCrossing())
718             {
719 
720                 // Crossing, stop if order of events is not ok
721                 // Should go before the conflict vehicle
722                 // 1) downstream vehicle must supply sufficient space before conflict vehicle will enter
723                 // 2) must clear the conflict before the conflict vehicle will enter
724                 // 3) if leader decelerates with b, conflict vehicle should be able to safely delay entering conflict
725                 if (ttpDz.getDuration().multiplyBy(f).plus(gap).gt(tteCa.getDuration())
726                         || ttcOa.getDuration().multiplyBy(f).plus(gap).gt(tteCa.getDuration())
727                         || ttpDs.getDuration().multiplyBy(f).plus(gap).gt(tteCs.getDuration()))
728                 {
729                     return true;
730                 }
731 
732             }
733             else
734             {
735                 throw new RuntimeException(
736                         "Conflict is of unknown type " + conflict.getConflictType() + ", which is not merge nor a crossing.");
737             }
738         }
739 
740         // No conflict vehicle triggered stopping
741         return false;
742 
743     }
744 
745     /**
746      * Approach a stop conflict. Currently this is equal to approaching a give-way conflict.
747      * @param conflict HeadwayConflict; conflict
748      * @param leaders PerceptionCollectable&lt;HeadwayGTU,LaneBasedGTU&gt;; leaders
749      * @param speed Speed; current speed
750      * @param acceleration Acceleration; current acceleration
751      * @param vehicleLength Length; vehicle length
752      * @param parameters Parameters; parameters
753      * @param speedLimitInfo SpeedLimitInfo; speed limit info
754      * @param carFollowingModel CarFollowingModel; car-following model
755      * @param bType ParameterTypeAcceleration; parameter type for considered deceleration
756      * @return whether to stop for this conflict
757      * @throws ParameterException if a parameter is not defined
758      */
759     @SuppressWarnings("checkstyle:parameternumber")
760     public static boolean stopForStopConflict(final HeadwayConflict conflict,
761             final PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders, final Speed speed, final Acceleration acceleration,
762             final Length vehicleLength, final Parameters parameters, final SpeedLimitInfo speedLimitInfo,
763             final CarFollowingModel carFollowingModel, final ParameterTypeAcceleration bType) throws ParameterException
764     {
765         // TODO stopping
766         return stopForGiveWayConflict(conflict, leaders, speed, acceleration, vehicleLength, parameters, speedLimitInfo,
767                 carFollowingModel, bType);
768     }
769 
770     /**
771      * Approach an all-stop conflict.
772      * @param conflict HeadwayConflict; conflict to approach
773      * @param conflictPlans ConflictPlans; set of plans for conflict
774      * @return whether to stop for this conflict
775      */
776     public static boolean stopForAllStopConflict(final HeadwayConflict conflict, final ConflictPlans conflictPlans)
777     {
778         // TODO all-stop behavior
779 
780         if (conflictPlans.isStopPhaseRun(conflict.getStopLine()))
781         {
782             return false;
783         }
784 
785         return false;
786     }
787 
788     /**
789      * Returns whether the conflicting link is on the route of the given gtu.
790      * @param conflictingLink CrossSectionLink; conflicting link
791      * @param gtu HeadwayGTU; gtu
792      * @return whether the conflict is on the route of the given gtu
793      */
794     private static boolean isOnRoute(final CrossSectionLink conflictingLink, final HeadwayGTU gtu)
795     {
796         try
797         {
798             Route route = gtu.getRoute();
799             if (route == null)
800             {
801                 // conservative assumption: it's on the route (gtu should be upstream of the conflict)
802                 return true;
803             }
804             Node startNode = conflictingLink.getStartNode();
805             Node endNode = conflictingLink.getEndNode();
806             return route.contains(startNode) && route.contains(endNode)
807                     && Math.abs(route.indexOf(endNode) - route.indexOf(startNode)) == 1;
808         }
809         catch (UnsupportedOperationException uoe)
810         {
811             // conservative assumption: it's on the route (gtu should be upstream of the conflict)
812             return true;
813         }
814     }
815 
816     /**
817      * Returns a speed dependent distance needed behind the leader to completely pass the conflict.
818      * @param vehicleLength Length; vehicle length
819      * @param parameters Parameters; parameters
820      * @return speed dependent distance needed behind the leader to completely pass the conflict
821      * @throws ParameterException if parameter is not available
822      */
823     private static Length passableDistance(final Length vehicleLength, final Parameters parameters) throws ParameterException
824     {
825         return parameters.getParameter(S0).plus(vehicleLength);
826     }
827 
828     /**
829      * Holds the tactical plans of a driver considering conflicts. These are remembered for consistency. For instance, if the
830      * decision is made to yield as current deceleration suggests it's safe to do so, but the trajectory for stopping in front
831      * of the conflict results in deceleration slightly above what is considered safe deceleration, the plan should not be
832      * abandoned. Decelerations above what is considered safe deceleration may result due to numerical overshoot or other factor
833      * coming into play in car-following models. Many other examples exist where a driver sticks to a certain plan.
834      * <p>
835      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
836      * <br>
837      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
838      * <p>
839      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jun 7, 2016 <br>
840      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
841      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
842      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
843      */
844     public static final class ConflictPlans implements Blockable, Serializable
845     {
846 
847         /** */
848         private static final long serialVersionUID = 20160811L;
849 
850         /** Phases of navigating an all-stop intersection per intersection. */
851         private final HashMap<String, StopPhase> stopPhases = new HashMap<>();
852 
853         /** Estimated arrival times of vehicles at all-stop intersection. */
854         private final HashMap<String, Time> arrivalTimes = new HashMap<>();
855 
856         /** Indicator intent. */
857         private TurnIndicatorIntent indicatorIntent = TurnIndicatorIntent.NONE;
858 
859         /** Distance to object causing turn indicator intent. */
860         private Length indicatorObjectDistance = null;
861 
862         /** Whether the GTU is blocking conflicts. */
863         private boolean blocking;
864 
865         /**
866          * Clean any yield plan that was no longer kept active in the last evaluation of conflicts.
867          */
868         void cleanPlans()
869         {
870             this.indicatorIntent = TurnIndicatorIntent.NONE;
871             this.indicatorObjectDistance = null;
872         }
873 
874         /**
875          * Sets the estimated arrival time of a GTU.
876          * @param gtu AbstractHeadwayGTU; GTU
877          * @param time Time; estimated arrival time
878          */
879         void setArrivalTime(final AbstractHeadwayGTU gtu, final Time time)
880         {
881             this.arrivalTimes.put(gtu.getId(), time);
882         }
883 
884         /**
885          * Returns the estimated arrival time of given GTU.
886          * @param gtu AbstractHeadwayGTU; GTU
887          * @return estimated arrival time of given GTU
888          */
889         Time getArrivalTime(final AbstractHeadwayGTU gtu)
890         {
891             return this.arrivalTimes.get(gtu.getId());
892         }
893 
894         /**
895          * Sets the current phase to 'approach' for the given stop line.
896          * @param stopLine HeadwayStopLine; stop line
897          */
898         void setStopPhaseApproach(final HeadwayStopLine stopLine)
899         {
900             this.stopPhases.put(stopLine.getId(), StopPhase.APPROACH);
901         }
902 
903         /**
904          * Sets the current phase to 'yield' for the given stop line.
905          * @param stopLine HeadwayStopLine; stop line
906          * @throws RuntimeException if the phase was not set to approach before
907          */
908         void setStopPhaseYield(final HeadwayStopLine stopLine)
909         {
910             Throw.when(
911                     !this.stopPhases.containsKey(stopLine.getId())
912                             || !this.stopPhases.get(stopLine.getId()).equals(StopPhase.APPROACH),
913                     RuntimeException.class, "Yield stop phase is set for stop line that was not approached.");
914             this.stopPhases.put(stopLine.getId(), StopPhase.YIELD);
915         }
916 
917         /**
918          * Sets the current phase to 'run' for the given stop line.
919          * @param stopLine HeadwayStopLine; stop line
920          * @throws RuntimeException if the phase was not set to approach before
921          */
922         void setStopPhaseRun(final HeadwayStopLine stopLine)
923         {
924             Throw.when(!this.stopPhases.containsKey(stopLine.getId()), RuntimeException.class,
925                     "Run stop phase is set for stop line that was not approached.");
926             this.stopPhases.put(stopLine.getId(), StopPhase.YIELD);
927         }
928 
929         /**
930          * @param stopLine HeadwayStopLine; stop line
931          * @return whether the current phase is 'approach' for the given stop line
932          */
933         boolean isStopPhaseApproach(final HeadwayStopLine stopLine)
934         {
935             return this.stopPhases.containsKey(stopLine.getId())
936                     && this.stopPhases.get(stopLine.getId()).equals(StopPhase.APPROACH);
937         }
938 
939         /**
940          * @param stopLine HeadwayStopLine; stop line
941          * @return whether the current phase is 'yield' for the given stop line
942          */
943         boolean isStopPhaseYield(final HeadwayStopLine stopLine)
944         {
945             return this.stopPhases.containsKey(stopLine.getId())
946                     && this.stopPhases.get(stopLine.getId()).equals(StopPhase.YIELD);
947         }
948 
949         /**
950          * @param stopLine HeadwayStopLine; stop line
951          * @return whether the current phase is 'run' for the given stop line
952          */
953         boolean isStopPhaseRun(final HeadwayStopLine stopLine)
954         {
955             return this.stopPhases.containsKey(stopLine.getId()) && this.stopPhases.get(stopLine.getId()).equals(StopPhase.RUN);
956         }
957 
958         /** {@inheritDoc} */
959         @Override
960         public String toString()
961         {
962             return "ConflictPlans";
963         }
964 
965         /**
966          * @return indicatorIntent.
967          */
968         public TurnIndicatorIntent getIndicatorIntent()
969         {
970             return this.indicatorIntent;
971         }
972 
973         /**
974          * @return indicatorObjectDistance.
975          */
976         public Length getIndicatorObjectDistance()
977         {
978             return this.indicatorObjectDistance;
979         }
980 
981         /**
982          * @param intent TurnIndicatorIntent; indicator intent
983          * @param distance Length; distance to object pertaining to the turn indicator intent
984          */
985         public void setIndicatorIntent(final TurnIndicatorIntent intent, final Length distance)
986         {
987             if (this.indicatorObjectDistance == null || this.indicatorObjectDistance.gt(distance))
988             {
989                 this.indicatorIntent = intent;
990                 this.indicatorObjectDistance = distance;
991             }
992         }
993 
994         /** {@inheritDoc} */
995         @Override
996         public boolean isBlocking()
997         {
998             return this.blocking;
999         }
1000 
1001         /**
1002          * Sets the GTU as blocking conflicts or not.
1003          * @param blocking boolean; whether the GTU is blocking conflicts
1004          */
1005         public void setBlocking(final boolean blocking)
1006         {
1007             this.blocking = blocking;
1008         }
1009 
1010     }
1011 
1012     /**
1013      * Phases of navigating an all-stop intersection.
1014      * <p>
1015      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1016      * <br>
1017      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
1018      * <p>
1019      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jun 30, 2016 <br>
1020      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
1021      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
1022      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
1023      */
1024     private enum StopPhase
1025     {
1026         /** Approaching stop intersection. */
1027         APPROACH,
1028 
1029         /** Yielding for stop intersection. */
1030         YIELD,
1031 
1032         /** Running over stop intersection. */
1033         RUN;
1034     }
1035 
1036 }