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