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