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