1 package org.opentrafficsim.road.gtu.tactical.util;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5 import java.util.LinkedHashMap;
6 import java.util.LinkedHashSet;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Optional;
10 import java.util.Set;
11 import java.util.UUID;
12 import java.util.function.Function;
13 import java.util.function.Supplier;
14
15 import org.djunits.unit.AccelerationUnit;
16 import org.djunits.unit.DurationUnit;
17 import org.djunits.unit.LengthUnit;
18 import org.djunits.value.vdouble.scalar.Acceleration;
19 import org.djunits.value.vdouble.scalar.Duration;
20 import org.djunits.value.vdouble.scalar.Length;
21 import org.djunits.value.vdouble.scalar.Speed;
22 import org.djunits.value.vdouble.scalar.Time;
23 import org.djutils.exceptions.Throw;
24 import org.opentrafficsim.base.OtsRuntimeException;
25 import org.opentrafficsim.base.logger.Logger;
26 import org.opentrafficsim.base.parameters.ParameterException;
27 import org.opentrafficsim.base.parameters.ParameterTypeAcceleration;
28 import org.opentrafficsim.base.parameters.ParameterTypeBoolean;
29 import org.opentrafficsim.base.parameters.ParameterTypeDouble;
30 import org.opentrafficsim.base.parameters.ParameterTypeDuration;
31 import org.opentrafficsim.base.parameters.ParameterTypeLength;
32 import org.opentrafficsim.base.parameters.ParameterTypes;
33 import org.opentrafficsim.base.parameters.Parameters;
34 import org.opentrafficsim.base.parameters.constraint.ConstraintInterface;
35 import org.opentrafficsim.core.definitions.DefaultsNl;
36 import org.opentrafficsim.core.gtu.GtuException;
37 import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
38 import org.opentrafficsim.core.network.Node;
39 import org.opentrafficsim.core.network.route.Route;
40 import org.opentrafficsim.road.gtu.LaneBasedGtu;
41 import org.opentrafficsim.road.gtu.perception.PerceptionCollectable;
42 import org.opentrafficsim.road.gtu.perception.PerceptionCollectable.PerceptionAccumulator;
43 import org.opentrafficsim.road.gtu.perception.PerceptionCollectable.PerceptionCollector;
44 import org.opentrafficsim.road.gtu.perception.PerceptionIterable;
45 import org.opentrafficsim.road.gtu.perception.RelativeLane;
46 import org.opentrafficsim.road.gtu.perception.categories.IntersectionPerception;
47 import org.opentrafficsim.road.gtu.perception.categories.neighbors.NeighborsPerception;
48 import org.opentrafficsim.road.gtu.perception.object.PerceivedConflict;
49 import org.opentrafficsim.road.gtu.perception.object.PerceivedGtu;
50 import org.opentrafficsim.road.gtu.perception.object.PerceivedGtu.Maneuver;
51 import org.opentrafficsim.road.gtu.perception.object.PerceivedGtu.Signals;
52 import org.opentrafficsim.road.gtu.perception.object.PerceivedGtuBase;
53 import org.opentrafficsim.road.gtu.perception.object.PerceivedGtuSimple;
54 import org.opentrafficsim.road.gtu.perception.object.PerceivedObject;
55 import org.opentrafficsim.road.gtu.perception.object.PerceivedObject.Kinematics;
56 import org.opentrafficsim.road.gtu.tactical.Blockable;
57 import org.opentrafficsim.road.gtu.tactical.TacticalContext;
58 import org.opentrafficsim.road.gtu.tactical.TacticalContextEgo;
59 import org.opentrafficsim.road.gtu.tactical.lmrs.AccelerationIncentive;
60 import org.opentrafficsim.road.gtu.tactical.pt.BusSchedule;
61 import org.opentrafficsim.road.network.CrossSectionLink;
62 import org.opentrafficsim.road.network.conflict.BusStopConflictRule;
63 import org.opentrafficsim.road.network.conflict.ConflictRule;
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public final class ConflictUtil
85 {
86
87
88 public static final ParameterTypeDuration MIN_GAP = new ParameterTypeDuration("minGap", "Minimum gap for conflicts",
89 new Duration(0.000001, DurationUnit.SECOND), ConstraintInterface.POSITIVE);
90
91
92 public static final ParameterTypeAcceleration B = ParameterTypes.B;
93
94
95 public static final ParameterTypeAcceleration BCRIT = ParameterTypes.BCRIT;
96
97
98 public static final ParameterTypeLength S0 = ParameterTypes.S0;
99
100
101 public static final ParameterTypeLength S0_CONF = new ParameterTypeLength("s0conf", "Stopping distance at conflicts",
102 new Length(1.5, LengthUnit.METER), ConstraintInterface.POSITIVE);
103
104
105 public static final ParameterTypeDouble TIME_FACTOR =
106 new ParameterTypeDouble("timeFactor", "Safety factor on estimated time", 1.25, ConstraintInterface.ATLEASTONE);
107
108
109 public static final ParameterTypeLength STOP_AREA =
110 new ParameterTypeLength("stopArea", "Area before stop line where one is considered arrived at the intersection",
111 new Length(4, LengthUnit.METER), ConstraintInterface.POSITIVE);
112
113
114 public static final ParameterTypeDuration TI = new ParameterTypeDuration("ti", "Indicator time before bus departure",
115 Duration.ofSI(3.0), ConstraintInterface.POSITIVE);
116
117
118 public static final ParameterTypeBoolean DEV_SPLIT =
119 new ParameterTypeBoolean("dev_split", "Deviate laterally at splits.", false);
120
121
122 private static final Duration TIME_STEP = Duration.ofSI(0.5);
123
124
125 private static final boolean CROSSSTANDING = true;
126
127
128 private static final ThreadLocal<Map<String, Set<String>>> CROSSEVENTS =
129 ThreadLocal.withInitial(() -> new LinkedHashMap<>());
130
131
132
133
134 private ConflictUtil()
135 {
136
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153 @SuppressWarnings("checkstyle:methodlength")
154
155 public static Acceleration approachConflicts(final TacticalContextEgo context, final ConflictPlans conflictPlans,
156 final RelativeLane lane, final Length mergeDistance, final boolean onRoute) throws GtuException, ParameterException
157 {
158 Iterable<PerceivedConflict> conflicts =
159 context.getPerception().getPerceptionCategory(IntersectionPerception.class).getConflicts(lane);
160 conflicts = AccelerationIncentive.onRoad(conflicts, lane, mergeDistance);
161 if (onRoute)
162 {
163 conflicts = AccelerationIncentive.onRoute(conflicts, context.getRoute().orElse(null));
164 }
165 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> leaders =
166 context.getPerception().getPerceptionCategory(NeighborsPerception.class).getLeaders(lane);
167
168 boolean blocking = false;
169
170
171 Acceleration a = Acceleration.POS_MAXVALUE;
172 Length stoppingDistance = Length.ofSI(context.getParameters().getParameter(S0).si + context.getLength().si
173 + .5 * context.getSpeed().si * context.getSpeed().si / context.getParameters().getParameter(B).si);
174 Iterator<PerceivedConflict> it = conflicts.iterator();
175 if (it.hasNext() && it.next().getDistance().gt(stoppingDistance))
176 {
177 conflictPlans.setBlocking(blocking);
178 return a;
179 }
180
181
182 List<Length> prevStarts = new ArrayList<>();
183 List<Length> prevEnds = new ArrayList<>();
184 List<Class<? extends ConflictRule>> conflictRuleTypes = new ArrayList<>();
185
186
187 Space space = leaders.collect(new AvailableSpace());
188 Length availableSpace = space.availableSpace().minus(passableDistance(context.getLength(), context.getParameters()));
189
190
191
192
193
194
195 Length firstLeader = leaders.isEmpty() ? Length.POS_MAXVALUE : leaders.first().getDistance();
196 boolean first = true;
197 for (PerceivedConflict conflict : conflicts)
198 {
199 if (conflict.getDistance().gt(space.firstStationary()))
200 {
201 break;
202 }
203
204 if (!conflict.isSplit())
205 {
206 Length conflictEnd = conflict.getDistance().plus(conflict.getLength());
207 if ((!first || conflict.isCrossing()) && conflictEnd.gt(firstLeader))
208 {
209 Length effectiveEnd = Length.min(space.firstStationary(), conflictEnd);
210 Length effectiveLength = effectiveEnd.minus(conflict.getDistance());
211 availableSpace = availableSpace.minus(effectiveLength);
212 }
213 first = false;
214 }
215 }
216
217 for (PerceivedConflict conflict : conflicts)
218 {
219
220 if (conflict.isCrossing())
221 {
222
223 a = Acceleration.min(a, avoidCrossingCollision(context, conflict));
224 }
225 else
226 {
227 if (conflict.isMerge() && !lane.isCurrent() && conflict.getConflictPriority().isPriority())
228 {
229
230 a = Acceleration.min(a, avoidMergeCollision(context, conflict));
231 }
232
233
234 lateralDeviationAtSplit(context, conflict, leaders, availableSpace);
235
236
237 a = Acceleration.min(a, followConflictingLeaderOnMergeOrSplit(context, conflict));
238 }
239
240
241 if (lane.isCurrent())
242 {
243
244
245 Optional<Route> route = context.getRoute();
246 if (route.isPresent() && route.get() instanceof BusSchedule busSchedule
247 && context.getGtuType().isOfType(DefaultsNl.BUS)
248 && conflict.getConflictRuleType().equals(BusStopConflictRule.class))
249 {
250 Optional<Duration> actualDeparture = busSchedule.getActualDepartureConflict(conflict.getId());
251 if (actualDeparture.isPresent()
252 && actualDeparture.get().si < context.getTime().si + context.getParameters().getParameter(TI).si)
253 {
254
255 context.addIntent(TurnIndicatorStatus.LEFT, conflict.getDistance());
256 }
257 }
258 }
259
260
261 if (conflict.getDistance().lt0() && lane.isCurrent())
262 {
263 if (conflict.getConflictType().isCrossing() && !conflict.getConflictPriority().isPriority())
264 {
265
266 blocking = true;
267 }
268
269 continue;
270 }
271
272
273 boolean stop = false;
274 if (conflict.isMerge() && conflict.getConflictPriority().isPriority())
275 {
276 if (conflict.getUpstreamConflictingGtus().isEmpty()
277 || !conflictPlans.isZipGtu(conflict.getUpstreamConflictingGtus().first().getId()))
278 {
279 conflictPlans.clearZipGtu();
280 }
281 else
282 {
283 stop = true;
284 }
285 }
286
287
288 if (!stop)
289 {
290 Length d = conflict.isCrossing() ? conflict.getDistance().plus(conflict.getLength()) : conflict.getDistance();
291 stop = !conflict.getConflictType().isSplit() && d.lt(space.firstStationary()) && availableSpace.lt(d);
292
293
294
295
296
297
298
299
300
301
302
303 if (stop && conflict.isMerge() && conflict.getConflictPriority().isPriority() && conflict.getDistance().gt0()
304 && !conflict.getUpstreamConflictingGtus().isEmpty() && conflict.getUpstreamConflictingGtus().first()
305 .getDistance().lt(context.getParameters().getParameter(STOP_AREA)))
306 {
307 conflictPlans.setZipGtu(conflict.getUpstreamConflictingGtus().first().getId());
308 }
309 }
310
311 if (!stop)
312 {
313 switch (conflict.getConflictPriority())
314 {
315 case PRIORITY:
316 {
317
318 break;
319 }
320 case YIELD:
321 {
322 Length prevEnd = prevEnds.isEmpty() ? null : prevEnds.get(prevEnds.size() - 1);
323 stop = stopForGiveWayConflict(context, conflict, leaders, blocking ? BCRIT : B, prevEnd);
324 break;
325 }
326 case STOP:
327 {
328 Length prevEnd = prevEnds.isEmpty() ? null : prevEnds.get(prevEnds.size() - 1);
329 stop = stopForStopConflict(context, conflict, leaders, blocking ? BCRIT : B, prevEnd);
330 break;
331 }
332 case ALL_STOP:
333 {
334 stop = stopForAllStopConflict(conflict, conflictPlans);
335 break;
336 }
337 case SPLIT:
338 {
339 continue;
340 }
341 default:
342 {
343 throw new GtuException("Unsupported conflict rule encountered while approaching conflicts.");
344 }
345 }
346 }
347
348
349 if (stop)
350 {
351 prevStarts.add(conflict.getDistance());
352 conflictRuleTypes.add(conflict.getConflictRuleType());
353
354
355 int j = 0;
356 for (int i = prevEnds.size() - 1; i >= 0; i--)
357 {
358
359 if (prevStarts.get(i + 1).minus(prevEnds.get(i))
360 .gt(passableDistance(context.getLength(), context.getParameters())))
361 {
362 j = i + 1;
363 break;
364 }
365 }
366 if (blocking && j == 0)
367 {
368
369 j = prevStarts.size() - 1;
370 }
371
372
373 context.getParameters().setParameterResettable(S0, context.getParameters().getParameter(S0_CONF));
374 Acceleration bCrit = context.getParameters().getParameter(ParameterTypes.BCRIT).neg();
375 Acceleration aConflict = Acceleration.ofSI(-Double.MAX_VALUE);
376 while (aConflict.si < bCrit.si && j < prevStarts.size())
377 {
378 if (prevStarts.get(j).lt(context.getParameters().getParameter(S0_CONF)))
379 {
380
381
382 aConflict = Acceleration.max(aConflict, bCrit);
383 }
384 else
385 {
386 Acceleration aStop = CarFollowingUtil.stop(context, prevStarts.get(j));
387 if (conflictRuleTypes.get(j).equals(BusStopConflictRule.class) && aStop.lt(bCrit))
388 {
389
390 aStop = Acceleration.POS_MAXVALUE;
391 }
392 aConflict = Acceleration.max(aConflict, aStop);
393 }
394 j++;
395 }
396 context.getParameters().resetParameter(S0);
397 a = Acceleration.min(a, aConflict);
398 break;
399 }
400
401
402 if (conflict.isCrossing())
403 {
404 prevStarts.add(conflict.getDistance());
405 conflictRuleTypes.add(conflict.getConflictRuleType());
406 prevEnds.add(conflict.getDistance().plus(conflict.getLength()));
407 }
408 }
409 conflictPlans.setBlocking(blocking);
410
411 if (a.si < -6.0 && context.getSpeed().si > 5.0 / 3.6)
412 {
413 Logger.ots().info("Deceleration from conflict util stronger than 6m/s^2.");
414
415 }
416 return a;
417 }
418
419
420
421
422
423
424
425
426 private static Acceleration followConflictingLeaderOnMergeOrSplit(final TacticalContext context,
427 final PerceivedConflict conflict) throws ParameterException
428 {
429
430 PerceptionIterable<PerceivedGtu> downstreamGTUs = conflict.getDownstreamConflictingGtus();
431 if (downstreamGTUs.isEmpty() || downstreamGTUs.first().getKinematics().getOverlap().isAhead())
432 {
433 return Acceleration.POS_MAXVALUE;
434 }
435
436 PerceivedGtu c = null;
437 Length virtualDistance = null;
438 if (conflict.getDistance().gt0())
439 {
440 c = downstreamGTUs.first();
441 virtualDistance = getVirtualDistance(c, conflict);
442 }
443 else
444 {
445 for (PerceivedGtu con : downstreamGTUs)
446 {
447 if (con.getKinematics().getOverlap().isAhead())
448 {
449
450 return Acceleration.POS_MAXVALUE;
451 }
452
453 virtualDistance = getVirtualDistance(con, conflict);
454 if (virtualDistance.gt0())
455 {
456
457 if (conflict.isSplit())
458 {
459 double conflictWidth = conflict.getWidthAtFraction(
460 (-conflict.getDistance().si + virtualDistance.si) / conflict.getConflictingLength().si).si;
461 double gtuWidth = con.getWidth().si + context.getWidth().si;
462 if (conflictWidth > gtuWidth)
463 {
464 continue;
465 }
466 }
467
468 c = con;
469 break;
470 }
471 }
472 }
473 if (c == null)
474 {
475
476 return Acceleration.POS_MAXVALUE;
477 }
478
479 Acceleration a = CarFollowingUtil.followSingleLeader(context, virtualDistance, c.getSpeed());
480
481
482 if (conflict.isMerge() && virtualDistance.lt(conflict.getDistance()))
483 {
484
485
486
487
488
489
490
491
492 context.getParameters().setParameterResettable(S0, context.getParameters().getParameter(S0_CONF));
493 Acceleration aStop = CarFollowingUtil.stop(context, conflict.getDistance());
494 context.getParameters().resetParameter(S0);
495 a = Acceleration.max(a, aStop);
496 }
497 return a;
498 }
499
500
501
502
503
504
505
506
507 private static void lateralDeviationAtSplit(final TacticalContextEgo context, final PerceivedConflict conflict,
508 final PerceptionCollectable<PerceivedGtu, LaneBasedGtu> leaders, final Length availableSpace)
509 {
510
511
512
513
514
515 if (conflict.isSplit() && !conflict.getTurn().isNone()
516 && context.getParameters().getOptionalParameter(DEV_SPLIT).orElse(false)
517 && (!conflict.getLane().getLink().equals(conflict.getConflictingLink()) || conflict.getDistance().lt0()))
518 {
519
520
521 Length relevantDistance = null;
522
523
524 if (!leaders.isEmpty() && leaders.first().getDistance().gt(conflict.getDistance()))
525 {
526 relevantDistance = leaders.first().getDistance();
527 }
528
529 var conflictingLeaders = conflict.getDownstreamConflictingGtus();
530 if (!conflictingLeaders.isEmpty())
531 {
532 Length virtualDistance = getVirtualDistance(conflictingLeaders.first(), conflict);
533 relevantDistance = relevantDistance == null ? virtualDistance : Length.min(relevantDistance, virtualDistance);
534 }
535
536
537
538
539
540 if (relevantDistance != null && (conflict.getDistance().plus(conflict.getLength()).gt(availableSpace)
541 || (relevantDistance.lt(availableSpace) && !conflictingLeaders.isEmpty()
542 && conflictingLeaders.first().getSpeed().eq0())))
543 {
544
545 Length positionAtWidthToConsider = Length.max(Length.ZERO, conflict.getDistance().neg());
546 Length laneWidth = conflict.getLane().getWidth(positionAtWidthToConsider);
547 Length deviation = laneWidth.times(0.5).minus(context.getWidth().times(0.5));
548 if (conflict.getTurn().isRight())
549 {
550 deviation = deviation.neg();
551 }
552
553
554 context.addIntent(deviation, Length.max(Length.ZERO, relevantDistance));
555 }
556 }
557 }
558
559
560
561
562
563
564
565
566 private static Length getVirtualDistance(final PerceivedGtu conflictingVehicle, final PerceivedConflict conflict)
567 {
568 if (conflictingVehicle.getKinematics().getOverlap().isAhead())
569 {
570 return conflict.getDistance().plus(conflict.getLength()).plus(conflictingVehicle.getDistance());
571 }
572 if (conflictingVehicle.getKinematics().getOverlap().isBehind())
573 {
574 return conflict.getDistance().minus(conflictingVehicle.getDistance()).minus(conflictingVehicle.getLength());
575 }
576
577
578
579
580
581
582
583
584 return conflict.getDistance().plus(conflictingVehicle.getKinematics().getOverlap().getOverlapRear().get());
585 }
586
587
588
589
590
591
592
593
594 private static Acceleration avoidCrossingCollision(final TacticalContext context, final PerceivedConflict conflict)
595 throws ParameterException
596 {
597
598 List<PerceivedGtu> conflictingGTUs = new ArrayList<>();
599 for (PerceivedGtu gtu : conflict.getUpstreamConflictingGtus())
600 {
601 if (conflict.getConflictingVisibility().lt(gtu.getDistance()))
602 {
603 break;
604 }
605 if (isOnRoute(conflict.getConflictingLink(), gtu))
606 {
607
608 conflictingGTUs.add(gtu);
609 break;
610 }
611 }
612 for (PerceivedGtu gtu : conflict.getDownstreamConflictingGtus())
613 {
614 if (gtu.getKinematics().getOverlap().isParallel())
615 {
616 conflictingGTUs.add(gtu);
617 }
618 else
619 {
620
621 break;
622 }
623 }
624
625 if (conflictingGTUs.isEmpty())
626 {
627 return Acceleration.POS_MAXVALUE;
628 }
629
630 Acceleration a = Acceleration.POS_MAXVALUE;
631 for (PerceivedGtu conflictingGTU : conflictingGTUs)
632 {
633
634 AnticipationInfo tteCz;
635 Length distance;
636 if (conflictingGTU.getKinematics().getOverlap().isParallel())
637 {
638 tteCz = new AnticipationInfo(Duration.ZERO, conflictingGTU.getSpeed());
639 distance = conflictingGTU.getKinematics().getOverlap().getOverlapRear().get().abs()
640 .plus(conflictingGTU.getKinematics().getOverlap().getOverlap().get()).plus(Length.max(Length.ZERO,
641 conflictingGTU.getKinematics().getOverlap().getOverlapFront().get().neg()));
642 }
643 else
644 {
645 tteCz = AnticipationInfo.anticipateMovement(conflictingGTU.getDistance(), conflictingGTU.getSpeed(),
646 Acceleration.ZERO);
647 distance = conflictingGTU.getDistance().plus(conflict.getLength()).plus(conflictingGTU.getLength());
648 }
649
650 AnticipationInfo ttcCz =
651 AnticipationInfo.anticipateMovement(distance, conflictingGTU.getSpeed(), Acceleration.ZERO);
652
653 AnticipationInfo tteOa =
654 AnticipationInfo.anticipateMovementFreeAcceleration(context, conflict.getDistance(), TIME_STEP);
655
656
657 if (tteCz.duration().lt(tteOa.duration()) && tteOa.duration().lt(ttcCz.duration()))
658 {
659 if (!conflictingGTU.getSpeed().eq0() || !CROSSSTANDING)
660 {
661 double t = ttcCz.duration().si;
662
663 double acc = 2.0 * (conflict.getDistance().si - context.getSpeed().si * t) / (t * t);
664
665 if (context.getSpeed().si / -acc > ttcCz.duration().si)
666 {
667 a = Acceleration.min(a, new Acceleration(acc, AccelerationUnit.SI));
668 }
669 else
670 {
671
672 a = Acceleration.min(a, CarFollowingUtil.stop(context, conflict.getDistance()));
673 }
674 }
675 else
676 {
677
678 if (tteOa.duration()
679 .lt(context.getParameters().getOptionalParameter(ParameterTypes.DT).orElse(Duration.ofSI(0.5))))
680 {
681
682 Map<String, Set<String>> map = CROSSEVENTS.get();
683 if (map.computeIfAbsent(context.getId(), (id) -> new LinkedHashSet<>()).add(conflictingGTU.getId()))
684 {
685 int count = map.values().stream().reduce(0, (c, s) -> Integer.valueOf(c + s.size()),
686 (c1, c2) -> Integer.valueOf(c1 + c2));
687 Logger.ots().info("GTU {} passes through GTU {} at crossing [{}].", context.getId(),
688 conflictingGTU.getId(), count);
689 }
690 }
691 }
692 }
693 }
694 return a;
695 }
696
697
698
699
700
701
702
703
704 private static Acceleration avoidMergeCollision(final TacticalContext context, final PerceivedConflict conflict)
705 throws ParameterException
706 {
707 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> conflicting = conflict.getUpstreamConflictingGtus();
708
709 if (conflicting.isEmpty() || conflicting.first().getKinematics().getOverlap().isParallel())
710 {
711 return Acceleration.POS_MAXVALUE;
712 }
713
714 PerceivedGtu conflictingGtu = conflicting.first();
715 double tteC = conflictingGtu.getDistance().si / conflictingGtu.getSpeed().si;
716 if (tteC < conflict.getDistance().si / context.getSpeed().si + 3.0)
717 {
718 return CarFollowingUtil.stop(context, conflict.getDistance());
719 }
720 return Acceleration.POS_MAXVALUE;
721 }
722
723
724
725
726
727
728
729
730
731
732
733 @SuppressWarnings({"checkstyle:parameternumber", "checkstyle:methodlength"})
734 public static boolean stopForGiveWayConflict(final TacticalContext context, final PerceivedConflict conflict,
735 final PerceptionCollectable<PerceivedGtu, LaneBasedGtu> leaders, final ParameterTypeAcceleration bType,
736 final Length prevEnd) throws ParameterException
737 {
738
739 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> conflictingVehiclesCollectable =
740 conflict.getUpstreamConflictingGtus();
741 Iterable<PerceivedGtu> conflictingVehicles;
742 if (conflictingVehiclesCollectable.isEmpty())
743 {
744 if (conflict.getConflictingTrafficLightDistance().isEmpty())
745 {
746
747 Length length = Length.ofSI(4.0);
748 PerceivedGtuSimple conflictGtu = new PerceivedGtuSimple("virtual " + UUID.randomUUID().toString(),
749 DefaultsNl.CAR, length, Length.ofSI(2.0),
750 Kinematics.dynamicBehind(conflict.getConflictingVisibility(),
751 conflict.getConflictingSpeedLimit().speed(), Acceleration.ZERO, true, length,
752 conflict.getLength()),
753 Signals.NONE, Maneuver.NONE);
754 conflictingVehicles = Set.of(conflictGtu);
755 }
756 else
757 {
758
759 return false;
760 }
761 }
762 else
763 {
764 PerceivedGtu conflicting = conflictingVehiclesCollectable.first();
765 Optional<Length> tlDistance = conflict.getConflictingTrafficLightDistance();
766 if (tlDistance.isPresent() && conflicting.getKinematics().getOverlap().isAhead()
767 && tlDistance.get().lt(conflicting.getDistance())
768 && (conflicting.getSpeed().eq0() || conflicting.getAcceleration().lt0()))
769 {
770
771 return false;
772 }
773 conflictingVehicles = conflictingVehiclesCollectable;
774 }
775
776
777 Acceleration b = context.getParameters().getParameter(bType).neg();
778 double f = context.getParameters().getParameter(TIME_FACTOR);
779 Duration gap = context.getParameters().getParameter(MIN_GAP);
780 Length passable = passableDistance(context.getLength(), context.getParameters());
781 Length distance = conflict.getDistance().plus(context.getLength());
782 if (conflict.isCrossing())
783 {
784 distance = distance.plus(conflict.getLength());
785 }
786
787
788 AnticipationInfo ttcOa = AnticipationInfo.anticipateMovementFreeAcceleration(context, distance, TIME_STEP);
789
790
791 boolean first = true;
792 for (PerceivedGtu conflictingVehicle : conflictingVehicles)
793 {
794
795 if (!isOnRoute(conflict.getConflictingLink(), conflictingVehicle))
796 {
797 continue;
798 }
799
800
801 if (first && conflictingVehicle.getSpeed().eq0() && conflictingVehicle.getKinematics().getOverlap().isAhead())
802 {
803 return false;
804 }
805
806
807 AnticipationInfo tteCa;
808 if (conflictingVehicle instanceof PerceivedGtuSimple)
809 {
810
811 tteCa = AnticipationInfo.anticipateMovement(conflictingVehicle.getDistance(), conflictingVehicle.getSpeed(),
812 conflictingVehicle.getAcceleration());
813 }
814 else
815 {
816
817 if (conflictingVehicle.getKinematics().getOverlap().isAhead())
818 {
819 tteCa = AnticipationInfo.anticipateMovementFreeAcceleration(conflictingVehicle,
820 conflictingVehicle.getDistance(), TIME_STEP);
821 }
822 else
823 {
824 tteCa = new AnticipationInfo(Duration.ZERO, conflictingVehicle.getSpeed());
825 }
826 }
827
828
829 if (conflict.isMerge())
830 {
831
832
833
834
835
836
837
838
839
840
841
842
843
844 double vSelf = ttcOa.endSpeed().si;
845 double speedDiff = conflictingVehicle.getSpeed().si - vSelf;
846 speedDiff = speedDiff > 0 ? speedDiff : 0;
847 Duration additionalTime = Duration.ofSI(speedDiff / -b.si);
848 double followerFront = conflictingVehicle.getSpeed().si * (ttcOa.duration().si + additionalTime.si)
849 - conflictingVehicle.getDistance().si + 0.5 * b.si * additionalTime.si * additionalTime.si;
850 double ownRear = vSelf * additionalTime.si;
851 Duration tMax = context.getParameters().getParameter(ParameterTypes.TMAX);
852 Length s0 = context.getParameters().getParameter(S0);
853
854
855 if (ttcOa.duration().times(f).plus(gap).gt(tteCa.duration()) || (!Double.isInfinite(tteCa.duration().si)
856 && tteCa.duration().si > 0.0 && ownRear < (followerFront + (tMax.si + gap.si) * vSelf + s0.si) * f))
857 {
858 return true;
859 }
860 }
861 else if (conflict.isCrossing())
862 {
863
864 AnticipationInfo ttpDz = null;
865 if (!leaders.isEmpty())
866 {
867 distance = conflict.getDistance().minus(leaders.first().getDistance()).plus(conflict.getLength())
868 .plus(passable);
869 ttpDz = AnticipationInfo.anticipateMovement(distance, leaders.first().getSpeed(), Acceleration.ZERO);
870 }
871 else
872 {
873
874 ttpDz = new AnticipationInfo(Duration.ZERO, Speed.ZERO);
875 }
876
877
878 if (ttpDz.duration().times(f).plus(gap).gt(tteCa.duration())
879 || ttcOa.duration().times(f).plus(gap).gt(tteCa.duration()))
880 {
881 return true;
882 }
883 }
884 else
885 {
886 throw new RuntimeException(
887 "Conflict is of unknown type " + conflict.getConflictType() + ", which is not merge nor a crossing.");
888 }
889 first = false;
890 }
891
892
893 return false;
894 }
895
896
897
898
899
900
901
902
903
904
905
906 @SuppressWarnings("checkstyle:parameternumber")
907 public static boolean stopForStopConflict(final TacticalContext context, final PerceivedConflict conflict,
908 final PerceptionCollectable<PerceivedGtu, LaneBasedGtu> leaders, final ParameterTypeAcceleration bType,
909 final Length prevEnd) throws ParameterException
910 {
911
912 return stopForGiveWayConflict(context, conflict, leaders, bType, prevEnd);
913 }
914
915
916
917
918
919
920
921 public static boolean stopForAllStopConflict(final PerceivedConflict conflict, final ConflictPlans conflictPlans)
922 {
923
924 if (conflictPlans.isStopPhaseRun(conflict.getStopLine()))
925 {
926 return false;
927 }
928 return false;
929 }
930
931
932
933
934
935
936
937 private static boolean isOnRoute(final CrossSectionLink conflictingLink, final PerceivedGtu gtu)
938 {
939 try
940 {
941 Optional<Route> route = gtu.getBehavior().getRoute();
942 if (route.isEmpty())
943 {
944
945 return true;
946 }
947 Node startNode = conflictingLink.getStartNode();
948 Node endNode = conflictingLink.getEndNode();
949 return route.get().contains(startNode) && route.get().contains(endNode)
950 && Math.abs(route.get().indexOf(endNode) - route.get().indexOf(startNode)) == 1;
951 }
952 catch (UnsupportedOperationException uoe)
953 {
954
955 return true;
956 }
957 }
958
959
960
961
962
963
964
965
966 private static Length passableDistance(final Length vehicleLength, final Parameters parameters) throws ParameterException
967 {
968 return parameters.getParameter(S0).plus(vehicleLength);
969 }
970
971
972
973
974
975
976
977
978 public static final class ConflictPlans implements Blockable
979 {
980
981
982 private final LinkedHashMap<String, StopPhase> stopPhases = new LinkedHashMap<>();
983
984
985 private final LinkedHashMap<String, Time> arrivalTimes = new LinkedHashMap<>();
986
987
988 private boolean blocking;
989
990
991 private String zipGtuId;
992
993
994
995
996 public ConflictPlans()
997 {
998
999 }
1000
1001
1002
1003
1004
1005
1006 void setArrivalTime(final PerceivedGtuBase gtu, final Time time)
1007 {
1008 this.arrivalTimes.put(gtu.getId(), time);
1009 }
1010
1011
1012
1013
1014
1015
1016 Time getArrivalTime(final PerceivedGtuBase gtu)
1017 {
1018 return this.arrivalTimes.get(gtu.getId());
1019 }
1020
1021
1022
1023
1024
1025 void setStopPhaseApproach(final PerceivedObject stopLine)
1026 {
1027 this.stopPhases.put(stopLine.getId(), StopPhase.APPROACH);
1028 }
1029
1030
1031
1032
1033
1034
1035 void setStopPhaseYield(final PerceivedObject stopLine)
1036 {
1037 Throw.when(
1038 !this.stopPhases.containsKey(stopLine.getId())
1039 || !this.stopPhases.get(stopLine.getId()).equals(StopPhase.APPROACH),
1040 OtsRuntimeException.class, "Yield stop phase is set for stop line that was not approached.");
1041 this.stopPhases.put(stopLine.getId(), StopPhase.YIELD);
1042 }
1043
1044
1045
1046
1047
1048
1049 void setStopPhaseRun(final PerceivedObject stopLine)
1050 {
1051 Throw.when(!this.stopPhases.containsKey(stopLine.getId()), OtsRuntimeException.class,
1052 "Run stop phase is set for stop line that was not approached.");
1053 this.stopPhases.put(stopLine.getId(), StopPhase.YIELD);
1054 }
1055
1056
1057
1058
1059
1060
1061 boolean isStopPhaseApproach(final PerceivedObject stopLine)
1062 {
1063 return this.stopPhases.containsKey(stopLine.getId())
1064 && this.stopPhases.get(stopLine.getId()).equals(StopPhase.APPROACH);
1065 }
1066
1067
1068
1069
1070
1071
1072 boolean isStopPhaseYield(final PerceivedObject stopLine)
1073 {
1074 return this.stopPhases.containsKey(stopLine.getId())
1075 && this.stopPhases.get(stopLine.getId()).equals(StopPhase.YIELD);
1076 }
1077
1078
1079
1080
1081
1082
1083 boolean isStopPhaseRun(final PerceivedObject stopLine)
1084 {
1085 return this.stopPhases.containsKey(stopLine.getId()) && this.stopPhases.get(stopLine.getId()).equals(StopPhase.RUN);
1086 }
1087
1088 @Override
1089 public boolean isBlocking()
1090 {
1091 return this.blocking;
1092 }
1093
1094
1095
1096
1097
1098 public void setBlocking(final boolean blocking)
1099 {
1100 this.blocking = blocking;
1101 }
1102
1103
1104
1105
1106
1107 void setZipGtu(final String zipGtuId)
1108 {
1109 this.zipGtuId = zipGtuId;
1110 }
1111
1112
1113
1114
1115 void clearZipGtu()
1116 {
1117 this.zipGtuId = null;
1118 }
1119
1120
1121
1122
1123
1124
1125 boolean isZipGtu(final String id)
1126 {
1127 return id.equals(this.zipGtuId);
1128 }
1129
1130 @Override
1131 public String toString()
1132 {
1133 return "ConflictPlans";
1134 }
1135
1136 }
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149 private enum StopPhase
1150 {
1151
1152 APPROACH,
1153
1154
1155 YIELD,
1156
1157
1158 RUN;
1159 }
1160
1161
1162
1163
1164 private static final class AvailableSpace implements PerceptionCollector<Space, LaneBasedGtu, Space>
1165 {
1166 @Override
1167 public Supplier<Space> getIdentity()
1168 {
1169 return () -> new Space();
1170 }
1171
1172 @Override
1173 public PerceptionAccumulator<LaneBasedGtu, Space> getAccumulator()
1174 {
1175 return (i, u, h) ->
1176 {
1177 if (i.getObject().addVehicle(u, h))
1178 {
1179 i.stop();
1180 }
1181 return i;
1182 };
1183 }
1184
1185 @Override
1186 public Function<Space, Space> getFinalizer()
1187 {
1188 return (l) -> l;
1189 }
1190 }
1191
1192
1193
1194
1195 private static final class Space
1196 {
1197
1198 private Length cumulativeRequiredSpace = Length.ZERO;
1199
1200
1201 private Length firstStationary;
1202
1203
1204
1205
1206
1207
1208
1209 public boolean addVehicle(final LaneBasedGtu gtu, final Length h)
1210 {
1211 if (gtu.getSpeed().si < 5.0 / 3.6 && gtu.getAcceleration().le0())
1212 {
1213 this.firstStationary = h;
1214 return true;
1215 }
1216 Length s0;
1217 try
1218 {
1219 s0 = gtu.getParameters().getParameter(ParameterTypes.S0);
1220 }
1221 catch (ParameterException ex)
1222 {
1223 s0 = Length.ofSI(3.0);
1224 }
1225 this.cumulativeRequiredSpace = this.cumulativeRequiredSpace.plus(gtu.getLength()).plus(s0);
1226 return false;
1227 }
1228
1229
1230
1231
1232
1233 public Length availableSpace()
1234 {
1235 return this.firstStationary == null ? Length.POSITIVE_INFINITY
1236 : this.firstStationary.minus(this.cumulativeRequiredSpace);
1237 }
1238
1239
1240
1241
1242
1243 public Length firstStationary()
1244 {
1245 return this.firstStationary == null ? Length.POSITIVE_INFINITY : this.firstStationary;
1246 }
1247 }
1248
1249 }