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