View Javadoc
1   package org.opentrafficsim.road.network.lane.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.event.Event;
18  import org.djutils.event.EventListener;
19  import org.djutils.exceptions.Throw;
20  import org.djutils.exceptions.Try;
21  import org.opentrafficsim.base.OtsRuntimeException;
22  import org.opentrafficsim.base.parameters.ParameterException;
23  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
24  import org.opentrafficsim.core.gtu.GtuException;
25  import org.opentrafficsim.core.gtu.RelativePosition;
26  import org.opentrafficsim.core.network.NetworkException;
27  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
28  import org.opentrafficsim.road.gtu.lane.perception.AbstractPerceptionReiterable;
29  import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
30  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.PerceivedGtuType;
31  import org.opentrafficsim.road.gtu.lane.perception.object.PerceivedGtu;
32  import org.opentrafficsim.road.gtu.lane.perception.structure.LaneRecordInterface;
33  import org.opentrafficsim.road.gtu.lane.perception.structure.NavigatingIterable;
34  import org.opentrafficsim.road.gtu.lane.perception.structure.NavigatingIterable.Entry;
35  import org.opentrafficsim.road.gtu.lane.perception.structure.SimpleLaneRecord;
36  import org.opentrafficsim.road.network.lane.Lane;
37  import org.opentrafficsim.road.network.lane.object.AbstractLaneBasedObject;
38  import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
39  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
40  
41  /**
42   * Conflicts deal with traffic on different links/roads that need to consider each other as their paths may be in conflict
43   * spatially. A single {@code Conflict} represents the one-sided consideration of a conflicting situation. I.e., what is
44   * considered <i>a single conflict in traffic theory, is represented by two {@code Conflict}s</i>, one on each of the
45   * conflicting {@code Lane}s.<br>
46   * <br>
47   * This class provides easy access to upstream and downstream GTUs through {@code PerceptionIterable}s using methods
48   * {@code getUpstreamGtus} and {@code getDownstreamGtus}. These methods are efficient in that they reuse underlying data
49   * structures if the GTUs are requested at the same time by another GTU.
50   * <p>
51   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
52   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
53   * </p>
54   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
55   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
56   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
57   */
58  public final class Conflict extends AbstractLaneBasedObject implements EventListener
59  {
60  
61      /** Conflict type, i.e. crossing, merge or split. */
62      private final ConflictType conflictType;
63  
64      /** Conflict rule, i.e. priority, give way, stop or all-stop. */
65      private final ConflictRule conflictRule;
66  
67      /** End of conflict. */
68      private final ConflictEnd end;
69  
70      /** Accompanying other conflict. */
71      private Conflict otherConflict;
72  
73      /** The length of the conflict along the lane centerline. */
74      private final Length length;
75  
76      /** Whether the conflict is a permitted conflict in traffic light control. */
77      private final boolean permitted;
78  
79      /** Distance to upstream traffic light. */
80      private Length trafficLightDistance;
81  
82      /** Maximum maximum search distance. */
83      private Length maxMaxTrafficLightDistance;
84  
85      /////////////////////////////////////////////////////////////////
86      // Properties regarding upstream and downstream GTUs provision //
87      /////////////////////////////////////////////////////////////////
88  
89      /** Root for GTU search. */
90      private final SimpleLaneRecord root;
91  
92      /** Position on the root. */
93      private final Length rootPosition;
94  
95      /** Current upstream GTUs provider. */
96      private Iterable<Entry<LaneBasedGtu>> upstreamGtus;
97  
98      /** Upstream GTUs update time. */
99      private Duration upstreamTime;
100 
101     /** Lanes on which upstream GTUs are found. */
102     private Map<LaneBasedGtu, Lane> upstreamLanes = new LinkedHashMap<>();
103 
104     /** Current downstream GTUs provider. */
105     private Iterable<Entry<LaneBasedGtu>> downstreamGtus;
106 
107     /** Downstream GTUs update time. */
108     private Duration downstreamTime;
109 
110     /** Lanes on which downstream GTUs are found. */
111     private Map<LaneBasedGtu, Lane> downstreamLanes = new LinkedHashMap<>();
112 
113     /** Distance within which upstreamGTUs are provided (is automatically enlarged). */
114     private Length maxUpstreamVisibility = Length.ZERO;
115 
116     /** Distance within which downstreamGTUs are provided (is automatically enlarged). */
117     private Length maxDownstreamVisibility = Length.ZERO;
118 
119     /** Set of upstream GTU that invalidate the iterable when any changes lane. */
120     private Set<LaneBasedGtu> upstreamListening = new LinkedHashSet<>();
121 
122     /** Set of upstream GTU that invalidate the iterable when any changes lane. */
123     private Set<LaneBasedGtu> downstreamListening = new LinkedHashSet<>();
124 
125     /////////////////////////////////////////////////////////////////
126 
127     /**
128      * Construct a new Conflict.
129      * @param lane lane where this conflict starts
130      * @param longitudinalPosition position of start of conflict on lane
131      * @param length length of the conflict along the lane centerline
132      * @param contour contour of conflict
133      * @param conflictType conflict type, i.e. crossing, merge or split
134      * @param conflictRule conflict rule, i.e. determines priority, give way, stop or all-stop
135      * @param permitted whether the conflict is permitted in traffic light control
136      * @throws NetworkException when the position on the lane is out of bounds
137      */
138     @SuppressWarnings("checkstyle:parameternumber")
139     private Conflict(final Lane lane, final Length longitudinalPosition, final Length length, final Polygon2d contour,
140             final ConflictType conflictType, final ConflictRule conflictRule, final boolean permitted) throws NetworkException
141     {
142         super(UUID.randomUUID().toString(), lane, longitudinalPosition, LaneBasedObject.makeLine(lane, longitudinalPosition),
143                 contour);
144         this.length = length;
145         this.conflictType = conflictType;
146         this.conflictRule = conflictRule;
147         this.permitted = permitted;
148 
149         // Create conflict end
150         if (conflictType.equals(ConflictType.SPLIT) || conflictType.equals(ConflictType.MERGE))
151         {
152             Length position = conflictType.equals(ConflictType.SPLIT) ? length : lane.getLength();
153             this.end = new ConflictEnd(this, lane, position);
154         }
155         else
156         {
157             this.end = null;
158         }
159 
160         // Lane record for GTU provision
161         this.rootPosition = longitudinalPosition;
162         this.root = new SimpleLaneRecord(lane, this.rootPosition.neg(), null);
163     }
164 
165     @Override
166     protected void init() throws NetworkException
167     {
168         super.init();
169         if (this.end != null)
170         {
171             this.end.init();
172         }
173     }
174 
175     /**
176      * Make sure the conflict can provide the given upstream visibility.
177      * @param visibility visibility to guarantee
178      */
179     private void provideUpstreamVisibility(final Length visibility)
180     {
181         if (visibility.gt(this.maxUpstreamVisibility))
182         {
183             this.maxUpstreamVisibility = visibility;
184             this.upstreamTime = null;
185             clearUpstreamListening();
186         }
187     }
188 
189     /**
190      * Make sure the conflict can provide the given downstream visibility.
191      * @param visibility visibility to guarantee
192      */
193     private void provideDownstreamVisibility(final Length visibility)
194     {
195         if (visibility.gt(this.maxDownstreamVisibility))
196         {
197             this.maxDownstreamVisibility = visibility;
198             this.downstreamTime = null;
199             clearDownstreamListening();
200         }
201     }
202 
203     /**
204      * Clear any listening to upstream GTUs.
205      */
206     private void clearUpstreamListening()
207     {
208         for (LaneBasedGtu gtu : this.upstreamListening)
209         {
210             if (!this.downstreamListening.contains(gtu))
211             {
212                 gtu.removeListener(this, LaneBasedGtu.LANE_CHANGE_EVENT);
213             }
214         }
215         this.upstreamListening.clear();
216     }
217 
218     /**
219      * Clear any listening to downstream GTUs.
220      */
221     private void clearDownstreamListening()
222     {
223         for (LaneBasedGtu gtu : this.downstreamListening)
224         {
225             if (!this.upstreamListening.contains(gtu))
226             {
227                 gtu.removeListener(this, LaneBasedGtu.LANE_CHANGE_EVENT);
228             }
229         }
230         this.downstreamListening.clear();
231     }
232 
233     /**
234      * Provides the upstream GTUs.
235      * @param perceivingGtu perceiving GTU
236      * @param perceivedGtuType perceived GTU type to use
237      * @param visibility distance over which GTU's are provided
238      * @return iterable over the upstream GTUs
239      */
240     public PerceptionCollectable<PerceivedGtu, LaneBasedGtu> getUpstreamGtus(final LaneBasedGtu perceivingGtu,
241             final PerceivedGtuType perceivedGtuType, final Length visibility)
242     {
243         provideUpstreamVisibility(visibility);
244         Duration time = this.getLane().getLink().getSimulator().getSimulatorTime();
245         if (this.upstreamTime == null || !time.eq(this.upstreamTime))
246         {
247             clearUpstreamListening();
248             // setup a base iterable to provide the GTUs
249             this.upstreamLanes.clear();
250             this.upstreamGtus = new NavigatingIterable<LaneBasedGtu, SimpleLaneRecord>(LaneBasedGtu.class,
251                     this.maxUpstreamVisibility, Set.of(this.root), (l) -> l.getPrev(), (l) ->
252                     {
253                         // this lister finds the relevant sublist of GTUs and reverses it
254                         List<LaneBasedGtu> gtus = l.getLane().getGtuList().toList();
255                         if (gtus.isEmpty())
256                         {
257                             return gtus;
258                         }
259                         int from = 0;
260                         while (from < gtus.size() && position(gtus.get(from), l, RelativePosition.REFERENCE).lt0())
261                         {
262                             from++;
263                         }
264                         int to = gtus.size() - 1;
265                         Length pos = Length.min(l.getStartDistance().neg(), l.getLength());
266                         while (to >= 0 && position(gtus.get(to), l, RelativePosition.FRONT).gt(pos))
267                         {
268                             to--;
269                         }
270                         if (from > to)
271                         {
272                             return Collections.emptyList();
273                         }
274                         if (from > 0 || to < gtus.size() - 1)
275                         {
276                             gtus = gtus.subList(from, to + 1);
277                         }
278                         Collections.reverse(gtus);
279                         gtus.forEach((g) ->
280                         {
281                             this.upstreamLanes.put(g, l.getLane());
282                             g.addListener(this, LaneBasedGtu.LANE_CHANGE_EVENT);
283                             this.upstreamListening.add(g);
284                         });
285                         return gtus;
286                     }, (t, r) -> r.getStartDistance().neg().minus(position(t, r, RelativePosition.FRONT)));
287             this.upstreamTime = time;
288         }
289         // return iterable that uses the base iterable
290         return new ConflictGtuIterable(perceivingGtu, perceivedGtuType, visibility, false, new Reiterable(this.upstreamGtus));
291         // PK does not think this detects GTUs changing lane INTO a lane of concern. Is that bad?
292     }
293 
294     /**
295      * Provides the downstream GTUs.
296      * @param perceivingGtu perceiving GTU
297      * @param perceivedGtuType perceived GTU type to use
298      * @param visibility distance over which GTU's are provided
299      * @return iterable over the downstream GTUs
300      */
301     public PerceptionCollectable<PerceivedGtu, LaneBasedGtu> getDownstreamGtus(final LaneBasedGtu perceivingGtu,
302             final PerceivedGtuType perceivedGtuType, final Length visibility)
303     {
304         provideDownstreamVisibility(visibility);
305         Duration time = this.getLane().getLink().getSimulator().getSimulatorTime();
306         if (this.downstreamTime == null || !time.eq(this.downstreamTime))
307         {
308             clearDownstreamListening();
309             // setup a base iterable to provide the GTUs
310             this.downstreamLanes.clear();
311             this.downstreamGtus = new Reiterable(new NavigatingIterable<LaneBasedGtu, SimpleLaneRecord>(LaneBasedGtu.class,
312                     this.maxDownstreamVisibility, Set.of(this.root), (l) -> l.getNext(), (l) ->
313                     {
314                         // this lister finds the relevant sublist of GTUs
315                         List<LaneBasedGtu> gtus = l.getLane().getGtuList().toList();
316                         if (gtus.isEmpty())
317                         {
318                             return gtus;
319                         }
320                         int from = 0;
321                         Length pos = Length.max(l.getStartDistance().neg(), Length.ZERO);
322                         while (from < gtus.size() && position(gtus.get(from), l, RelativePosition.FRONT).lt(pos))
323                         {
324                             from++;
325                         }
326                         int to = gtus.size() - 1;
327                         while (to >= 0 && position(gtus.get(to), l, RelativePosition.REFERENCE).gt(l.getLength()))
328                         {
329                             to--;
330                         }
331                         if (from > to)
332                         {
333                             return Collections.emptyList();
334                         }
335                         if (from > 0 || to < gtus.size() - 1)
336                         {
337                             gtus = gtus.subList(from, to + 1);
338                         }
339                         gtus.forEach((g) ->
340                         {
341                             this.downstreamLanes.put(g, l.getLane());
342                             g.addListener(this, LaneBasedGtu.LANE_CHANGE_EVENT);
343                             this.downstreamListening.add(g);
344                         });
345                         return gtus;
346                     }, (t, r) -> r.getStartDistance().plus(position(t, r, RelativePosition.REAR))));
347             this.downstreamTime = time;
348         }
349         // return iterable that uses the base iterable
350         return new ConflictGtuIterable(perceivingGtu, perceivedGtuType, visibility, true, this.downstreamGtus);
351         // PK does not think this detects GTUs changing lane INTO a lane of concern. Is that bad?
352     }
353 
354     /**
355      * Returns the position of the GTU on the lane of the given record.
356      * @param gtu gtu.
357      * @param record lane record.
358      * @param positionType RelativePosition.Type; relative position type.
359      * @return position of the GTU on the lane of the given record.
360      */
361     private Length position(final LaneBasedGtu gtu, final LaneRecordInterface<?> record,
362             final RelativePosition.Type positionType)
363     {
364         return Try.assign(() -> gtu.getPosition(record.getLane(), gtu.getRelativePositions().get(positionType)),
365                 "Unable to obtain position %s of GTU.", positionType);
366     }
367 
368     @Override
369     public void notify(final Event event)
370     {
371         Object[] payload = (Object[]) event.getContent();
372         LaneBasedGtu gtu = (LaneBasedGtu) getLane().getNetwork().getGTU((String) payload[0])
373                 .orElseThrow(() -> new OtsRuntimeException("Lane-change event on GTU not in the network."));
374         if (this.upstreamListening.contains(gtu))
375         {
376             this.upstreamTime = null;
377             clearUpstreamListening();
378         }
379         if (this.downstreamListening.contains(gtu))
380         {
381             this.downstreamTime = null;
382             clearDownstreamListening();
383         }
384     }
385 
386     /**
387      * Returns the conflict type.
388      * @return conflictType.
389      */
390     public ConflictType getConflictType()
391     {
392         return this.conflictType;
393     }
394 
395     /**
396      * Returns the conflict rule.
397      * @return conflictRule.
398      */
399     public ConflictRule getConflictRule()
400     {
401         return this.conflictRule;
402     }
403 
404     /**
405      * Returns the conflict priority.
406      * @return conflictPriority.
407      */
408     public ConflictPriority conflictPriority()
409     {
410         return this.conflictRule.determinePriority(this);
411     }
412 
413     @Override
414     public Length getLength()
415     {
416         return this.length;
417     }
418 
419     /**
420      * Returns the other conflict.
421      * @return otherConflict.
422      */
423     public Conflict getOtherConflict()
424     {
425         return this.otherConflict;
426     }
427 
428     /**
429      * If permitted, traffic upstream of traffic lights may not be ignored, as these can have green light.
430      * @return permitted.
431      */
432     public boolean isPermitted()
433     {
434         return this.permitted;
435     }
436 
437     /**
438      * Returns the distance to an upstream traffic light.
439      * @param maxDistance maximum distance of traffic light
440      * @return distance to upstream traffic light, infinite if beyond maximum distance
441      */
442     public Length getTrafficLightDistance(final Length maxDistance)
443     {
444         if (this.trafficLightDistance == null)
445         {
446             if (this.maxMaxTrafficLightDistance == null || this.maxMaxTrafficLightDistance.lt(maxDistance))
447             {
448                 this.maxMaxTrafficLightDistance = maxDistance;
449                 NavigatingIterable<TrafficLight, SimpleLaneRecord> iterable =
450                         new NavigatingIterable<>(TrafficLight.class, maxDistance, Set.of(this.root), (l) ->
451                         {
452                             // this navigator only returns records when there are no TrafficLights on the lane
453                             List<LaneBasedObject> list =
454                                     l.getLane().getLaneBasedObjects(Length.ZERO, l.getStartDistance().neg());
455                             if (list.stream().anyMatch((o) -> o instanceof TrafficLight))
456                             {
457                                 return Collections.emptySet();
458                             }
459                             return l.getPrev();
460                         }, (l) ->
461                         {
462                             // this lister finds the first TrafficLight and returns it as the only TrafficLight in the list
463                             List<LaneBasedObject> list =
464                                     l.getLane().getLaneBasedObjects(Length.ZERO, l.getStartDistance().neg());
465                             for (int index = list.size() - 1; index >= 0; index--)
466                             {
467                                 if (list.get(index) instanceof TrafficLight)
468                                 {
469                                     return List.of(list.get(index));
470                                 }
471                             }
472                             return Collections.emptyList();
473                         }, (t, l) -> l.getStartDistance().neg().minus(t.getLongitudinalPosition()));
474                 Iterator<Entry<TrafficLight>> iterator = iterable.iterator();
475                 if (iterator.hasNext())
476                 {
477                     this.trafficLightDistance = iterator.next().distance();
478                 }
479             }
480         }
481         if (this.trafficLightDistance != null && maxDistance.ge(this.trafficLightDistance))
482         {
483             return this.trafficLightDistance;
484         }
485         return Length.POSITIVE_INFINITY;
486     }
487 
488     /**
489      * Creates a pair of conflicts.
490      * @param conflictType conflict type, i.e. crossing, merge or split
491      * @param conflictRule conflict rule
492      * @param permitted whether the conflict is permitted in traffic light control
493      * @param lane1 lane of conflict 1
494      * @param longitudinalPosition1 longitudinal position of conflict 1
495      * @param length1 {@code Length} of conflict 1
496      * @param geometry1 geometry of conflict 1
497      * @param lane2 lane of conflict 2
498      * @param longitudinalPosition2 longitudinal position of conflict 2
499      * @param length2 {@code Length} of conflict 2
500      * @param geometry2 geometry of conflict 2
501      * @param simulator the simulator for animation and timed events
502      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
503      */
504     @SuppressWarnings("checkstyle:parameternumber")
505     public static void generateConflictPair(final ConflictType conflictType, final ConflictRule conflictRule,
506             final boolean permitted, final Lane lane1, final Length longitudinalPosition1, final Length length1,
507             final Polygon2d geometry1, final Lane lane2, final Length longitudinalPosition2, final Length length2,
508             final Polygon2d geometry2, final OtsSimulatorInterface simulator) throws NetworkException
509     {
510         // lane, longitudinalPosition, length and geometry are checked in AbstractLaneBasedObject
511         Throw.whenNull(conflictType, "Conflict type may not be null.");
512 
513         Conflict conf1 = new Conflict(lane1, longitudinalPosition1, length1, geometry1, conflictType, conflictRule, permitted);
514         conf1.init(); // fire events and register on lane
515         Conflict conf2 = new Conflict(lane2, longitudinalPosition2, length2, geometry2, conflictType, conflictRule, permitted);
516         conf2.init(); // fire events and register on lane
517         conf1.otherConflict = conf2;
518         conf2.otherConflict = conf1;
519     }
520 
521     @Override
522     public String toString()
523     {
524         return "Conflict [conflictType=" + this.conflictType + ", conflictRule=" + this.conflictRule + "]";
525     }
526 
527     /**
528      * Light-weight lane based object to indicate the end of a conflict. It is used to perceive conflicts when a GTU is on the
529      * conflict area, and hence the conflict lane based object is upstream.
530      */
531     public class ConflictEnd extends AbstractLaneBasedObject
532     {
533         /** Conflict at start of conflict area. */
534         private final Conflict conflict;
535 
536         /**
537          * Construct a new ConflictEnd object.
538          * @param conflict conflict at start of conflict area
539          * @param lane lane
540          * @param longitudinalPosition position along the lane of the end of the conflict
541          * @throws NetworkException on network exception
542          */
543         ConflictEnd(final Conflict conflict, final Lane lane, final Length longitudinalPosition) throws NetworkException
544         {
545             super(conflict.getId() + "End", lane, longitudinalPosition, LaneBasedObject.makeLine(lane, longitudinalPosition));
546             this.conflict = conflict;
547         }
548 
549         @Override
550         public void init() throws NetworkException
551         {
552             // override makes init accessible to conflict
553             super.init();
554         }
555 
556         /**
557          * Returns the conflict.
558          * @return conflict
559          */
560         public final Conflict getConflict()
561         {
562             return this.conflict;
563         }
564 
565         @Override
566         public final String toString()
567         {
568             return "ConflictEnd [conflict=" + this.conflict + "]";
569         }
570     }
571 
572     /**
573      * Iterable for upstream and downstream GTUs of a conflict, which uses a base iterable.
574      */
575     private class ConflictGtuIterable extends AbstractPerceptionReiterable<LaneBasedGtu, PerceivedGtu, LaneBasedGtu>
576     {
577         /** PerceivedGtu type. */
578         private final PerceivedGtuType perceptionGtuType;
579 
580         /** Guaranteed visibility. */
581         private final Length visibility;
582 
583         /** Downstream (or upstream) neighbors. */
584         private final boolean downstream;
585 
586         /** Base iterator of the base iterable. */
587         private final Iterator<Entry<LaneBasedGtu>> base;
588 
589         /**
590          * @param perceivingGtu perceiving GTU
591          * @param perceptionGtuType perceived Gtu type
592          * @param visibility guaranteed visibility
593          * @param downstream downstream (or upstream) neighbors
594          * @param base base iterable from the conflict
595          */
596         ConflictGtuIterable(final LaneBasedGtu perceivingGtu, final PerceivedGtuType perceptionGtuType, final Length visibility,
597                 final boolean downstream, final Iterable<Entry<LaneBasedGtu>> base)
598         {
599             super(perceivingGtu);
600             this.perceptionGtuType = perceptionGtuType;
601             this.visibility = visibility;
602             this.downstream = downstream;
603             this.base = base.iterator();
604         }
605 
606         @Override
607         protected Iterator<UnderlyingDistance<LaneBasedGtu>> primaryIterator()
608         {
609             /**
610              * Iterator that iterates over PrimaryIteratorEntry objects.
611              */
612             class ConflictGtuIterator implements Iterator<UnderlyingDistance<LaneBasedGtu>>
613             {
614                 /** Next entry. */
615                 private UnderlyingDistance<LaneBasedGtu> next;
616 
617                 @Override
618                 public boolean hasNext()
619                 {
620                     if (this.next == null)
621                     {
622                         if (ConflictGtuIterable.this.base.hasNext())
623                         {
624                             Entry<LaneBasedGtu> gtu = ConflictGtuIterable.this.base.next();
625                             if (gtu.object().getId().equals(getObject().getId()))
626                             {
627                                 if (ConflictGtuIterable.this.base.hasNext())
628                                 {
629                                     gtu = ConflictGtuIterable.this.base.next();
630                                 }
631                                 else
632                                 {
633                                     return false;
634                                 }
635                             }
636                             if (gtu.distance() == null || gtu.distance().le(ConflictGtuIterable.this.visibility))
637                             {
638                                 this.next = new UnderlyingDistance<>(gtu.object(), gtu.distance());
639                             }
640                         }
641                     }
642                     return this.next != null;
643                 }
644 
645                 @Override
646                 public UnderlyingDistance<LaneBasedGtu> next()
647                 {
648                     if (hasNext())
649                     {
650                         UnderlyingDistance<LaneBasedGtu> out = this.next;
651                         this.next = null;
652                         return out;
653                     }
654                     throw new NoSuchElementException();
655                 }
656             }
657             return new ConflictGtuIterator();
658         }
659 
660         @Override
661         protected PerceivedGtu perceive(final LaneBasedGtu object, final Length distance)
662                 throws GtuException, ParameterException
663         {
664             return this.perceptionGtuType.createPerceivedGtu(getObject(), Conflict.this, object,
665                     this.downstream ? distance.minus(getLength()) : distance, this.downstream);
666         }
667     }
668 
669     /**
670      * Reiterable of which the main purpose is efficiency. Storing the result for multiple GTUs is more efficient than invoking
671      * the NavigatingIterable logic for each.
672      */
673     private class Reiterable implements Iterable<Entry<LaneBasedGtu>>
674     {
675         /** Base iterator from NavigatingIterable. */
676         private final Iterator<Entry<LaneBasedGtu>> base;
677 
678         /** List of found GTUs so far. */
679         private final List<Entry<LaneBasedGtu>> soFar = new ArrayList<>();
680 
681         /**
682          * Constructor.
683          * @param base base iterable.
684          */
685         Reiterable(final Iterable<Entry<LaneBasedGtu>> base)
686         {
687             this.base = base.iterator();
688         }
689 
690         @Override
691         public Iterator<Entry<LaneBasedGtu>> iterator()
692         {
693             return new Iterator<Entry<LaneBasedGtu>>()
694             {
695                 private int index = 0;
696 
697                 @Override
698                 public Entry<LaneBasedGtu> next()
699                 {
700                     return Reiterable.this.soFar.get(this.index++);
701                 }
702 
703                 @Override
704                 public boolean hasNext()
705                 {
706                     if (this.index >= Reiterable.this.soFar.size() && Reiterable.this.base.hasNext())
707                     {
708                         Reiterable.this.soFar.add(Reiterable.this.base.next());
709                     }
710                     return this.index < Reiterable.this.soFar.size();
711                 }
712             };
713         }
714     }
715 
716 }