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