View Javadoc
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   * Conflicts deal with traffic on different links/roads that need to consider each other as their paths may be in conflict
44   * spatially. A single {@code Conflict} represents the one-sided consideration of a conflicting situation. I.e., what is
45   * considered <i>a single conflict in traffic theory, is represented by two {@code Conflict}s</i>, one on each of the
46   * conflicting {@code Lane}s.<br>
47   * <br>
48   * This class provides easy access to upstream and downstream GTUs through {@code PerceptionIterable}s using methods
49   * {@code getUpstreamGtus} and {@code getDownstreamGtus}. These methods are efficient in that they reuse underlying data
50   * structures if the GTUs are requested at the same time by another GTU.
51   * <p>
52   * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
53   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
54   * </p>
55   * @author Alexander Verbraeck
56   * @author Peter Knoppers
57   * @author Wouter Schakel
58   */
59  public final class Conflict extends AbstractLaneBasedObject implements EventListener
60  {
61  
62      /** Conflict type, i.e. crossing, merge or split. */
63      private final ConflictType conflictType;
64  
65      /** Conflict rule, i.e. priority, give way, stop or all-stop. */
66      private final ConflictRule conflictRule;
67  
68      /** End of conflict. */
69      private final ConflictEnd end;
70  
71      /** Accompanying other conflict. */
72      private Conflict otherConflict;
73  
74      /** The length of the conflict along the lane centerline. */
75      private final Length length;
76  
77      /** Whether the conflict is a permitted conflict in traffic light control. */
78      private final boolean permitted;
79  
80      /** Distance to upstream traffic light. */
81      private Length trafficLightDistance;
82  
83      /** Maximum maximum search distance. */
84      private Length maxMaxTrafficLightDistance;
85  
86      /** Turn direction of merge and split conflicts. */
87      private LateralDirectionality turn;
88  
89      /////////////////////////////////////////////////////////////////
90      // Properties regarding upstream and downstream GTUs provision //
91      /////////////////////////////////////////////////////////////////
92  
93      /** Root for GTU search. */
94      private final SimpleLaneRecord root;
95  
96      /** Position on the root. */
97      private final Length rootPosition;
98  
99      /** Current upstream GTUs provider. */
100     private Iterable<Entry<LaneBasedGtu>> upstreamGtus;
101 
102     /** Upstream GTUs update time. */
103     private Duration upstreamTime;
104 
105     /** Lanes on which upstream GTUs are found. */
106     private Map<LaneBasedGtu, Lane> upstreamLanes = new LinkedHashMap<>();
107 
108     /** Current downstream GTUs provider. */
109     private Iterable<Entry<LaneBasedGtu>> downstreamGtus;
110 
111     /** Downstream GTUs update time. */
112     private Duration downstreamTime;
113 
114     /** Lanes on which downstream GTUs are found. */
115     private Map<LaneBasedGtu, Lane> downstreamLanes = new LinkedHashMap<>();
116 
117     /** Distance within which upstreamGTUs are provided (is automatically enlarged). */
118     private Length maxUpstreamVisibility = Length.ZERO;
119 
120     /** Distance within which downstreamGTUs are provided (is automatically enlarged). */
121     private Length maxDownstreamVisibility = Length.ZERO;
122 
123     /** Set of upstream GTU that invalidate the iterable when any changes lane. */
124     private Set<LaneBasedGtu> upstreamListening = new LinkedHashSet<>();
125 
126     /** Set of upstream GTU that invalidate the iterable when any changes lane. */
127     private Set<LaneBasedGtu> downstreamListening = new LinkedHashSet<>();
128 
129     /////////////////////////////////////////////////////////////////
130 
131     /**
132      * Construct a new Conflict.
133      * @param lane lane where this conflict starts
134      * @param longitudinalPosition position of start of conflict on lane
135      * @param length length of the conflict along the lane centerline
136      * @param contour contour of conflict
137      * @param conflictType conflict type, i.e. crossing, merge or split
138      * @param conflictRule conflict rule, i.e. determines priority, give way, stop or all-stop
139      * @param permitted whether the conflict is permitted in traffic light control
140      * @throws NetworkException when the position on the lane is out of bounds
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         // Create conflict end
154         if (conflictType.equals(ConflictType.SPLIT) || conflictType.equals(ConflictType.MERGE))
155         {
156             /*
157              * This is skipped in the current implementation because LaneStructure.getDownstreamObjects() accounts for the
158              * object length. That does not work across lane boundaries, but that does not work for Conflicts anyway. No code in
159              * OTS actually searches for ConflictEnd objects. (skl - 2026.02.10)
160              */
161             // Length position = conflictType.equals(ConflictType.SPLIT) ? length : lane.getLength();
162             this.end = null; // new ConflictEnd(this, lane, position);
163         }
164         else
165         {
166             this.end = null;
167         }
168 
169         // Lane record for GTU provision
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      * Make sure the conflict can provide the given upstream visibility.
186      * @param visibility visibility to guarantee
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      * Make sure the conflict can provide the given downstream visibility.
200      * @param visibility visibility to guarantee
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      * Clear any listening to upstream GTUs.
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      * Clear any listening to downstream GTUs.
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      * Provides the upstream GTUs.
244      * @param perceivingGtu perceiving GTU
245      * @param perceivedGtuType perceived GTU type to use
246      * @param visibility distance over which GTU's are provided
247      * @return iterable over the upstream GTUs
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             // setup a base iterable to provide the GTUs
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                         // this lister finds the relevant sublist of GTUs and reverses it
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         // return iterable that uses the base iterable
299         return new ConflictGtuIterable(perceivingGtu, perceivedGtuType, visibility, false, new Reiterable(this.upstreamGtus));
300         // PK does not think this detects GTUs changing lane INTO a lane of concern. Is that bad?
301     }
302 
303     /**
304      * Provides the downstream GTUs.
305      * @param perceivingGtu perceiving GTU
306      * @param perceivedGtuType perceived GTU type to use
307      * @param visibility distance over which GTU's are provided
308      * @return iterable over the downstream GTUs
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             // setup a base iterable to provide the GTUs
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                         // this lister finds the relevant sublist of GTUs
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         // return iterable that uses the base iterable
359         return new ConflictGtuIterable(perceivingGtu, perceivedGtuType, visibility, true, this.downstreamGtus);
360         // PK does not think this detects GTUs changing lane INTO a lane of concern. Is that bad?
361     }
362 
363     /**
364      * Returns the position of the GTU on the lane of the given record.
365      * @param gtu gtu.
366      * @param record lane record.
367      * @param positionType RelativePosition.Type; relative position type.
368      * @return position of the GTU on the lane of the given record.
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      * Returns the conflict type.
396      * @return conflictType.
397      */
398     public ConflictType getConflictType()
399     {
400         return this.conflictType;
401     }
402 
403     /**
404      * Returns the conflict rule.
405      * @return conflictRule.
406      */
407     public ConflictRule getConflictRule()
408     {
409         return this.conflictRule;
410     }
411 
412     /**
413      * Returns the conflict priority.
414      * @return conflictPriority.
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      * Returns the other conflict.
429      * @return otherConflict.
430      */
431     public Conflict getOtherConflict()
432     {
433         return this.otherConflict;
434     }
435 
436     /**
437      * If permitted, traffic upstream of traffic lights may not be ignored, as these can have green light.
438      * @return permitted.
439      */
440     public boolean isPermitted()
441     {
442         return this.permitted;
443     }
444 
445     /**
446      * Returns the distance to an upstream traffic light.
447      * @param maxDistance maximum distance of traffic light
448      * @return distance to upstream traffic light, infinite if beyond maximum distance
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                             // this navigator only returns records when there are no TrafficLights on the lane
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                             // this lister finds the first TrafficLight and returns it as the only TrafficLight in the list
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      * Returns the turn direction of this conflict relative to the other conflict. This is NONE for crossing conflicts.
498      * @return turn direction of this conflict relative to the other conflict
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      * Creates a pair of conflicts.
551      * @param conflictType conflict type, i.e. crossing, merge or split
552      * @param conflictRule conflict rule
553      * @param permitted whether the conflict is permitted in traffic light control
554      * @param lane1 lane of conflict 1
555      * @param longitudinalPosition1 longitudinal position of conflict 1
556      * @param length1 {@code Length} of conflict 1
557      * @param geometry1 geometry of conflict 1
558      * @param lane2 lane of conflict 2
559      * @param longitudinalPosition2 longitudinal position of conflict 2
560      * @param length2 {@code Length} of conflict 2
561      * @param geometry2 geometry of conflict 2
562      * @param simulator the simulator for animation and timed events
563      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
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         // lane, longitudinalPosition, length and geometry are checked in AbstractLaneBasedObject
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(); // fire events and register on lane
576         Conflict conf2 = new Conflict(lane2, longitudinalPosition2, length2, geometry2, conflictType, conflictRule, permitted);
577         conf2.init(); // fire events and register on lane
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      * Light-weight lane based object to indicate the end of a conflict. It is used to perceive conflicts when a GTU is on the
590      * conflict area, and hence the conflict lane based object is upstream.
591      */
592     public class ConflictEnd extends AbstractLaneBasedObject
593     {
594         /** Conflict at start of conflict area. */
595         private final Conflict conflict;
596 
597         /**
598          * Construct a new ConflictEnd object.
599          * @param conflict conflict at start of conflict area
600          * @param lane lane
601          * @param longitudinalPosition position along the lane of the end of the conflict
602          * @throws NetworkException on network exception
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             // override makes init accessible to conflict
614             super.init();
615         }
616 
617         /**
618          * Returns the conflict.
619          * @return conflict
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      * Iterable for upstream and downstream GTUs of a conflict, which uses a base iterable.
635      */
636     private class ConflictGtuIterable extends AbstractPerceptionReiterable<LaneBasedGtu, PerceivedGtu, LaneBasedGtu>
637     {
638         /** PerceivedGtu type. */
639         private final PerceivedGtuType perceptionGtuType;
640 
641         /** Guaranteed visibility. */
642         private final Length visibility;
643 
644         /** Downstream (or upstream) neighbors. */
645         private final boolean downstream;
646 
647         /** Base iterator of the base iterable. */
648         private final Iterator<Entry<LaneBasedGtu>> base;
649 
650         /**
651          * @param perceivingGtu perceiving GTU
652          * @param perceptionGtuType perceived Gtu type
653          * @param visibility guaranteed visibility
654          * @param downstream downstream (or upstream) neighbors
655          * @param base base iterable from the conflict
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              * Iterator that iterates over PrimaryIteratorEntry objects.
672              */
673             class ConflictGtuIterator implements Iterator<DistancedObject<LaneBasedGtu>>
674             {
675                 /** Next entry. */
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      * Reiterable of which the main purpose is efficiency. Storing the result for multiple GTUs is more efficient than invoking
731      * the NavigatingIterable logic for each.
732      */
733     private class Reiterable implements Iterable<Entry<LaneBasedGtu>>
734     {
735         /** Base iterator from NavigatingIterable. */
736         private final Iterator<Entry<LaneBasedGtu>> base;
737 
738         /** List of found GTUs so far. */
739         private final List<Entry<LaneBasedGtu>> soFar = new ArrayList<>();
740 
741         /**
742          * Constructor.
743          * @param base base iterable.
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 }