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