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