1 package org.opentrafficsim.road.network.conflict;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.Iterator;
6 import java.util.LinkedHashMap;
7 import java.util.LinkedHashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.NoSuchElementException;
11 import java.util.Set;
12 import java.util.UUID;
13
14 import org.djunits.value.vdouble.scalar.Duration;
15 import org.djunits.value.vdouble.scalar.Length;
16 import org.djutils.draw.line.Polygon2d;
17 import org.djutils.draw.point.Point2d;
18 import org.djutils.event.Event;
19 import org.djutils.event.EventListener;
20 import org.djutils.exceptions.Throw;
21 import org.opentrafficsim.base.DistancedObject;
22 import org.opentrafficsim.base.OtsRuntimeException;
23 import org.opentrafficsim.base.parameters.ParameterException;
24 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
25 import org.opentrafficsim.core.gtu.RelativePosition;
26 import org.opentrafficsim.core.network.LateralDirectionality;
27 import org.opentrafficsim.core.network.NetworkException;
28 import org.opentrafficsim.road.gtu.LaneBasedGtu;
29 import org.opentrafficsim.road.gtu.perception.AbstractPerceptionReiterable;
30 import org.opentrafficsim.road.gtu.perception.PerceptionCollectable;
31 import org.opentrafficsim.road.gtu.perception.categories.neighbors.PerceivedGtuType;
32 import org.opentrafficsim.road.gtu.perception.object.PerceivedGtu;
33 import org.opentrafficsim.road.gtu.perception.structure.LaneRecordInterface;
34 import org.opentrafficsim.road.gtu.perception.structure.NavigatingIterable;
35 import org.opentrafficsim.road.gtu.perception.structure.NavigatingIterable.Entry;
36 import org.opentrafficsim.road.gtu.perception.structure.SimpleLaneRecord;
37 import org.opentrafficsim.road.network.Lane;
38 import org.opentrafficsim.road.network.object.AbstractLaneBasedObject;
39 import org.opentrafficsim.road.network.object.LaneBasedObject;
40 import org.opentrafficsim.road.network.object.trafficlight.TrafficLight;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public final class Conflict extends AbstractLaneBasedObject implements EventListener
60 {
61
62
63 private final ConflictType conflictType;
64
65
66 private final ConflictRule conflictRule;
67
68
69 private final ConflictEnd end;
70
71
72 private Conflict otherConflict;
73
74
75 private final Length length;
76
77
78 private final boolean permitted;
79
80
81 private Length trafficLightDistance;
82
83
84 private Length maxMaxTrafficLightDistance;
85
86
87 private LateralDirectionality turn;
88
89
90
91
92
93
94 private final SimpleLaneRecord root;
95
96
97 private final Length rootPosition;
98
99
100 private Iterable<Entry<LaneBasedGtu>> upstreamGtus;
101
102
103 private Duration upstreamTime;
104
105
106 private Map<LaneBasedGtu, Lane> upstreamLanes = new LinkedHashMap<>();
107
108
109 private Iterable<Entry<LaneBasedGtu>> downstreamGtus;
110
111
112 private Duration downstreamTime;
113
114
115 private Map<LaneBasedGtu, Lane> downstreamLanes = new LinkedHashMap<>();
116
117
118 private Length maxUpstreamVisibility = Length.ZERO;
119
120
121 private Length maxDownstreamVisibility = Length.ZERO;
122
123
124 private Set<LaneBasedGtu> upstreamListening = new LinkedHashSet<>();
125
126
127 private Set<LaneBasedGtu> downstreamListening = new LinkedHashSet<>();
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 @SuppressWarnings("checkstyle:parameternumber")
143 private Conflict(final Lane lane, final Length longitudinalPosition, final Length length, final Polygon2d contour,
144 final ConflictType conflictType, final ConflictRule conflictRule, final boolean permitted) throws NetworkException
145 {
146 super(UUID.randomUUID().toString(), lane, longitudinalPosition, LaneBasedObject.makeLine(lane, longitudinalPosition),
147 contour);
148 this.length = length;
149 this.conflictType = conflictType;
150 this.conflictRule = conflictRule;
151 this.permitted = permitted;
152
153
154 if (conflictType.equals(ConflictType.SPLIT) || conflictType.equals(ConflictType.MERGE))
155 {
156
157
158
159
160
161
162 this.end = null;
163 }
164 else
165 {
166 this.end = null;
167 }
168
169
170 this.rootPosition = longitudinalPosition;
171 this.root = new SimpleLaneRecord(lane, this.rootPosition.neg(), null);
172 }
173
174 @Override
175 protected void init() throws NetworkException
176 {
177 super.init();
178 if (this.end != null)
179 {
180 this.end.init();
181 }
182 }
183
184
185
186
187
188 private void provideUpstreamVisibility(final Length visibility)
189 {
190 if (visibility.gt(this.maxUpstreamVisibility))
191 {
192 this.maxUpstreamVisibility = visibility;
193 this.upstreamTime = null;
194 clearUpstreamListening();
195 }
196 }
197
198
199
200
201
202 private void provideDownstreamVisibility(final Length visibility)
203 {
204 if (visibility.gt(this.maxDownstreamVisibility))
205 {
206 this.maxDownstreamVisibility = visibility;
207 this.downstreamTime = null;
208 clearDownstreamListening();
209 }
210 }
211
212
213
214
215 private void clearUpstreamListening()
216 {
217 for (LaneBasedGtu gtu : this.upstreamListening)
218 {
219 if (!this.downstreamListening.contains(gtu))
220 {
221 gtu.removeListener(this, LaneBasedGtu.LANE_CHANGE_EVENT);
222 }
223 }
224 this.upstreamListening.clear();
225 }
226
227
228
229
230 private void clearDownstreamListening()
231 {
232 for (LaneBasedGtu gtu : this.downstreamListening)
233 {
234 if (!this.upstreamListening.contains(gtu))
235 {
236 gtu.removeListener(this, LaneBasedGtu.LANE_CHANGE_EVENT);
237 }
238 }
239 this.downstreamListening.clear();
240 }
241
242
243
244
245
246
247
248
249 public PerceptionCollectable<PerceivedGtu, LaneBasedGtu> getUpstreamGtus(final LaneBasedGtu perceivingGtu,
250 final PerceivedGtuType perceivedGtuType, final Length visibility)
251 {
252 provideUpstreamVisibility(visibility);
253 Duration time = this.getLane().getLink().getSimulator().getSimulatorTime();
254 if (this.upstreamTime == null || !time.eq(this.upstreamTime))
255 {
256 clearUpstreamListening();
257
258 this.upstreamLanes.clear();
259 this.upstreamGtus = new NavigatingIterable<LaneBasedGtu, SimpleLaneRecord>(LaneBasedGtu.class,
260 this.maxUpstreamVisibility, Set.of(this.root), (l) -> l.getPrev(), (l) ->
261 {
262
263 List<LaneBasedGtu> gtus = l.getLane().getGtuList().toList();
264 if (gtus.isEmpty())
265 {
266 return gtus;
267 }
268 int from = 0;
269 while (from < gtus.size() && position(gtus.get(from), l, RelativePosition.REFERENCE).lt0())
270 {
271 from++;
272 }
273 int to = gtus.size() - 1;
274 Length pos = Length.min(l.getStartDistance().neg(), l.getLength());
275 while (to >= 0 && position(gtus.get(to), l, RelativePosition.FRONT).gt(pos))
276 {
277 to--;
278 }
279 if (from > to)
280 {
281 return Collections.emptyList();
282 }
283 if (from > 0 || to < gtus.size() - 1)
284 {
285 gtus = gtus.subList(from, to + 1);
286 }
287 Collections.reverse(gtus);
288 gtus.forEach((g) ->
289 {
290 this.upstreamLanes.put(g, l.getLane());
291 g.addListener(this, LaneBasedGtu.LANE_CHANGE_EVENT);
292 this.upstreamListening.add(g);
293 });
294 return gtus;
295 }, (t, r) -> r.getStartDistance().neg().minus(position(t, r, RelativePosition.FRONT)));
296 this.upstreamTime = time;
297 }
298
299 return new ConflictGtuIterable(perceivingGtu, perceivedGtuType, visibility, false, new Reiterable(this.upstreamGtus));
300
301 }
302
303
304
305
306
307
308
309
310 public PerceptionCollectable<PerceivedGtu, LaneBasedGtu> getDownstreamGtus(final LaneBasedGtu perceivingGtu,
311 final PerceivedGtuType perceivedGtuType, final Length visibility)
312 {
313 provideDownstreamVisibility(visibility);
314 Duration time = this.getLane().getLink().getSimulator().getSimulatorTime();
315 if (this.downstreamTime == null || !time.eq(this.downstreamTime))
316 {
317 clearDownstreamListening();
318
319 this.downstreamLanes.clear();
320 this.downstreamGtus = new Reiterable(new NavigatingIterable<LaneBasedGtu, SimpleLaneRecord>(LaneBasedGtu.class,
321 this.maxDownstreamVisibility, Set.of(this.root), (l) -> l.getNext(), (l) ->
322 {
323
324 List<LaneBasedGtu> gtus = l.getLane().getGtuList().toList();
325 if (gtus.isEmpty())
326 {
327 return gtus;
328 }
329 int from = 0;
330 Length pos = Length.max(l.getStartDistance().neg(), Length.ZERO);
331 while (from < gtus.size() && position(gtus.get(from), l, RelativePosition.FRONT).lt(pos))
332 {
333 from++;
334 }
335 int to = gtus.size() - 1;
336 while (to >= 0 && position(gtus.get(to), l, RelativePosition.REFERENCE).gt(l.getLength()))
337 {
338 to--;
339 }
340 if (from > to)
341 {
342 return Collections.emptyList();
343 }
344 if (from > 0 || to < gtus.size() - 1)
345 {
346 gtus = gtus.subList(from, to + 1);
347 }
348 gtus.forEach((g) ->
349 {
350 this.downstreamLanes.put(g, l.getLane());
351 g.addListener(this, LaneBasedGtu.LANE_CHANGE_EVENT);
352 this.downstreamListening.add(g);
353 });
354 return gtus;
355 }, (t, r) -> r.getStartDistance().plus(position(t, r, RelativePosition.REAR))));
356 this.downstreamTime = time;
357 }
358
359 return new ConflictGtuIterable(perceivingGtu, perceivedGtuType, visibility, true, this.downstreamGtus);
360
361 }
362
363
364
365
366
367
368
369
370 private Length position(final LaneBasedGtu gtu, final LaneRecordInterface<?> record,
371 final RelativePosition.Type positionType)
372 {
373 return gtu.getPosition(record.getLane(), gtu.getRelativePositions().get(positionType));
374 }
375
376 @Override
377 public void notify(final Event event)
378 {
379 Object[] payload = (Object[]) event.getContent();
380 LaneBasedGtu gtu = (LaneBasedGtu) getLane().getNetwork().getGTU((String) payload[0])
381 .orElseThrow(() -> new OtsRuntimeException("Lane-change event on GTU not in the network."));
382 if (this.upstreamListening.contains(gtu))
383 {
384 this.upstreamTime = null;
385 clearUpstreamListening();
386 }
387 if (this.downstreamListening.contains(gtu))
388 {
389 this.downstreamTime = null;
390 clearDownstreamListening();
391 }
392 }
393
394
395
396
397
398 public ConflictType getConflictType()
399 {
400 return this.conflictType;
401 }
402
403
404
405
406
407 public ConflictRule getConflictRule()
408 {
409 return this.conflictRule;
410 }
411
412
413
414
415
416 public ConflictPriority conflictPriority()
417 {
418 return this.conflictRule.determinePriority(this);
419 }
420
421 @Override
422 public Length getLength()
423 {
424 return this.length;
425 }
426
427
428
429
430
431 public Conflict getOtherConflict()
432 {
433 return this.otherConflict;
434 }
435
436
437
438
439
440 public boolean isPermitted()
441 {
442 return this.permitted;
443 }
444
445
446
447
448
449
450 public Length getTrafficLightDistance(final Length maxDistance)
451 {
452 if (this.trafficLightDistance == null)
453 {
454 if (this.maxMaxTrafficLightDistance == null || this.maxMaxTrafficLightDistance.lt(maxDistance))
455 {
456 this.maxMaxTrafficLightDistance = maxDistance;
457 NavigatingIterable<TrafficLight, SimpleLaneRecord> iterable =
458 new NavigatingIterable<>(TrafficLight.class, maxDistance, Set.of(this.root), (l) ->
459 {
460
461 List<LaneBasedObject> list =
462 l.getLane().getLaneBasedObjects(Length.ZERO, l.getStartDistance().neg());
463 if (list.stream().anyMatch((o) -> o instanceof TrafficLight))
464 {
465 return Collections.emptySet();
466 }
467 return l.getPrev();
468 }, (l) ->
469 {
470
471 List<LaneBasedObject> list =
472 l.getLane().getLaneBasedObjects(Length.ZERO, l.getStartDistance().neg());
473 for (int index = list.size() - 1; index >= 0; index--)
474 {
475 if (list.get(index) instanceof TrafficLight)
476 {
477 return List.of(list.get(index));
478 }
479 }
480 return Collections.emptyList();
481 }, (t, l) -> l.getStartDistance().neg().minus(t.getLongitudinalPosition()));
482 Iterator<Entry<TrafficLight>> iterator = iterable.iterator();
483 if (iterator.hasNext())
484 {
485 this.trafficLightDistance = iterator.next().distance();
486 }
487 }
488 }
489 if (this.trafficLightDistance != null && maxDistance.ge(this.trafficLightDistance))
490 {
491 return this.trafficLightDistance;
492 }
493 return Length.POSITIVE_INFINITY;
494 }
495
496
497
498
499
500 public LateralDirectionality getTurn()
501 {
502 if (this.turn == null)
503 {
504 if (getConflictType().isCrossing())
505 {
506 this.turn = LateralDirectionality.NONE;
507 return this.turn;
508 }
509 Point2d pStart1 = getLocation();
510 Point2d pEnd1 = getLane().getCenterLine().getLocationExtended(getLongitudinalPosition().plus(getLength()));
511 Point2d pStart2 = getOtherConflict().getLocation();
512 Point2d pEnd2 = getOtherConflict().getLane().getCenterLine()
513 .getLocationExtended(getOtherConflict().getLongitudinalPosition().plus(getOtherConflict().getLength()));
514 double dx1 = pEnd1.x - pStart1.x;
515 double dy1 = pEnd1.y - pStart1.y;
516 double h = Math.hypot(dx1, dy1);
517 dx1 /= h;
518 dy1 /= h;
519 double dx2 = pEnd2.x - pStart2.x;
520 double dy2 = pEnd2.y - pStart2.y;
521 h = Math.hypot(dx2, dy2);
522 dx2 /= h;
523 dy2 /= h;
524 double cross = dx1 * dy2 - dy1 * dx2;
525 if (getConflictType().isMerge())
526 {
527 cross = -cross;
528 }
529 double eps = 1e-9;
530 if (cross > eps)
531 {
532 this.turn = LateralDirectionality.RIGHT;
533 getOtherConflict().turn = LateralDirectionality.LEFT;
534 }
535 else if (cross < -eps)
536 {
537 this.turn = LateralDirectionality.LEFT;
538 getOtherConflict().turn = LateralDirectionality.RIGHT;
539 }
540 else
541 {
542 this.turn = LateralDirectionality.NONE;
543 getOtherConflict().turn = LateralDirectionality.NONE;
544 }
545 }
546 return this.turn;
547 }
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565 @SuppressWarnings("checkstyle:parameternumber")
566 public static void generateConflictPair(final ConflictType conflictType, final ConflictRule conflictRule,
567 final boolean permitted, final Lane lane1, final Length longitudinalPosition1, final Length length1,
568 final Polygon2d geometry1, final Lane lane2, final Length longitudinalPosition2, final Length length2,
569 final Polygon2d geometry2, final OtsSimulatorInterface simulator) throws NetworkException
570 {
571
572 Throw.whenNull(conflictType, "Conflict type may not be null.");
573
574 Conflict conf1 = new Conflict(lane1, longitudinalPosition1, length1, geometry1, conflictType, conflictRule, permitted);
575 conf1.init();
576 Conflict conf2 = new Conflict(lane2, longitudinalPosition2, length2, geometry2, conflictType, conflictRule, permitted);
577 conf2.init();
578 conf1.otherConflict = conf2;
579 conf2.otherConflict = conf1;
580 }
581
582 @Override
583 public String toString()
584 {
585 return "Conflict [conflictType=" + this.conflictType + ", conflictRule=" + this.conflictRule + "]";
586 }
587
588
589
590
591
592 public class ConflictEnd extends AbstractLaneBasedObject
593 {
594
595 private final Conflict conflict;
596
597
598
599
600
601
602
603
604 ConflictEnd(final Conflict conflict, final Lane lane, final Length longitudinalPosition) throws NetworkException
605 {
606 super(conflict.getId() + "End", lane, longitudinalPosition, LaneBasedObject.makeLine(lane, longitudinalPosition));
607 this.conflict = conflict;
608 }
609
610 @Override
611 public void init() throws NetworkException
612 {
613
614 super.init();
615 }
616
617
618
619
620
621 public final Conflict getConflict()
622 {
623 return this.conflict;
624 }
625
626 @Override
627 public final String toString()
628 {
629 return "ConflictEnd [conflict=" + this.conflict + "]";
630 }
631 }
632
633
634
635
636 private class ConflictGtuIterable extends AbstractPerceptionReiterable<LaneBasedGtu, PerceivedGtu, LaneBasedGtu>
637 {
638
639 private final PerceivedGtuType perceptionGtuType;
640
641
642 private final Length visibility;
643
644
645 private final boolean downstream;
646
647
648 private final Iterator<Entry<LaneBasedGtu>> base;
649
650
651
652
653
654
655
656
657 ConflictGtuIterable(final LaneBasedGtu perceivingGtu, final PerceivedGtuType perceptionGtuType, final Length visibility,
658 final boolean downstream, final Iterable<Entry<LaneBasedGtu>> base)
659 {
660 super(perceivingGtu);
661 this.perceptionGtuType = perceptionGtuType;
662 this.visibility = visibility;
663 this.downstream = downstream;
664 this.base = base.iterator();
665 }
666
667 @Override
668 protected Iterator<DistancedObject<LaneBasedGtu>> primaryIterator()
669 {
670
671
672
673 class ConflictGtuIterator implements Iterator<DistancedObject<LaneBasedGtu>>
674 {
675
676 private DistancedObject<LaneBasedGtu> next;
677
678 @Override
679 public boolean hasNext()
680 {
681 if (this.next == null)
682 {
683 if (ConflictGtuIterable.this.base.hasNext())
684 {
685 Entry<LaneBasedGtu> gtu = ConflictGtuIterable.this.base.next();
686 if (gtu.object().getId().equals(getObject().getId()))
687 {
688 if (ConflictGtuIterable.this.base.hasNext())
689 {
690 gtu = ConflictGtuIterable.this.base.next();
691 }
692 else
693 {
694 return false;
695 }
696 }
697 if (gtu.distance() == null || gtu.distance().le(ConflictGtuIterable.this.visibility))
698 {
699 this.next = new DistancedObject<>(gtu.object(), gtu.distance());
700 }
701 }
702 }
703 return this.next != null;
704 }
705
706 @Override
707 public DistancedObject<LaneBasedGtu> next()
708 {
709 if (hasNext())
710 {
711 DistancedObject<LaneBasedGtu> out = this.next;
712 this.next = null;
713 return out;
714 }
715 throw new NoSuchElementException();
716 }
717 }
718 return new ConflictGtuIterator();
719 }
720
721 @Override
722 protected PerceivedGtu perceive(final LaneBasedGtu object, final Length distance) throws ParameterException
723 {
724 return this.perceptionGtuType.createPerceivedGtu(getObject(), Conflict.this, object,
725 this.downstream ? distance.minus(getLength()) : distance, this.downstream);
726 }
727 }
728
729
730
731
732
733 private class Reiterable implements Iterable<Entry<LaneBasedGtu>>
734 {
735
736 private final Iterator<Entry<LaneBasedGtu>> base;
737
738
739 private final List<Entry<LaneBasedGtu>> soFar = new ArrayList<>();
740
741
742
743
744
745 Reiterable(final Iterable<Entry<LaneBasedGtu>> base)
746 {
747 this.base = base.iterator();
748 }
749
750 @Override
751 public Iterator<Entry<LaneBasedGtu>> iterator()
752 {
753 return new Iterator<Entry<LaneBasedGtu>>()
754 {
755 private int index = 0;
756
757 @Override
758 public Entry<LaneBasedGtu> next()
759 {
760 return Reiterable.this.soFar.get(this.index++);
761 }
762
763 @Override
764 public boolean hasNext()
765 {
766 if (this.index >= Reiterable.this.soFar.size() && Reiterable.this.base.hasNext())
767 {
768 Reiterable.this.soFar.add(Reiterable.this.base.next());
769 }
770 return this.index < Reiterable.this.soFar.size();
771 }
772 };
773 }
774 }
775
776 }