View Javadoc
1   package org.opentrafficsim.road.network.lane.conflict;
2   
3   import java.rmi.RemoteException;
4   import java.util.Iterator;
5   import java.util.LinkedHashMap;
6   import java.util.LinkedHashSet;
7   import java.util.Map;
8   import java.util.NoSuchElementException;
9   import java.util.Set;
10  import java.util.UUID;
11  
12  import org.djunits.value.vdouble.scalar.Length;
13  import org.djunits.value.vdouble.scalar.Time;
14  import org.djutils.draw.line.Polygon2d;
15  import org.djutils.draw.point.Point2d;
16  import org.djutils.event.Event;
17  import org.djutils.event.EventListener;
18  import org.djutils.exceptions.Throw;
19  import org.djutils.exceptions.Try;
20  import org.opentrafficsim.base.parameters.ParameterException;
21  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
22  import org.opentrafficsim.core.geometry.OtsGeometryException;
23  import org.opentrafficsim.core.gtu.GtuException;
24  import org.opentrafficsim.core.gtu.RelativePosition;
25  import org.opentrafficsim.core.network.NetworkException;
26  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
27  import org.opentrafficsim.road.gtu.lane.perception.AbstractPerceptionIterable;
28  import org.opentrafficsim.road.gtu.lane.perception.AbstractPerceptionReiterable;
29  import org.opentrafficsim.road.gtu.lane.perception.DownstreamNeighborsIterable;
30  import org.opentrafficsim.road.gtu.lane.perception.LaneBasedObjectIterable;
31  import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
32  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
33  import org.opentrafficsim.road.gtu.lane.perception.UpstreamNeighborsIterable;
34  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.HeadwayGtuType;
35  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtu;
36  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtuReal;
37  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayTrafficLight;
38  import org.opentrafficsim.road.gtu.lane.perception.structure.LaneRecordInterface;
39  import org.opentrafficsim.road.gtu.lane.perception.structure.SimpleLaneRecord;
40  import org.opentrafficsim.road.network.lane.Lane;
41  import org.opentrafficsim.road.network.lane.object.AbstractLaneBasedObject;
42  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
43  
44  /**
45   * Conflicts deal with traffic on different links/roads that need to consider each other as their paths may be in conflict
46   * spatially. A single {@code Conflict} represents the one-sided consideration of a conflicting situation. I.e., what is
47   * considered <i>a single conflict in traffic theory, is represented by two {@code Conflict}s</i>, one on each of the
48   * conflicting {@code Lane}s.<br>
49   * <br>
50   * This class provides easy access to upstream and downstream GTUs through {@code PerceptionIterable}s using methods
51   * {@code getUpstreamGtus} and {@code getDownstreamGtus}. These methods are efficient in that they reuse underlying data
52   * structures if the GTUs are requested at the same time by another GTU.
53   * <p>
54   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
55   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
56   * </p>
57   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
58   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
59   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
60   */
61  public final class Conflict extends AbstractLaneBasedObject implements EventListener
62  {
63  
64      /** */
65      private static final long serialVersionUID = 20160915L;
66  
67      /** Conflict type, i.e. crossing, merge or split. */
68      private final ConflictType conflictType;
69  
70      /** Conflict rule, i.e. priority, give way, stop or all-stop. */
71      private final ConflictRule conflictRule;
72  
73      /** End of conflict. */
74      private final ConflictEnd end;
75  
76      /** Accompanying other conflict. */
77      private Conflict otherConflict;
78  
79      /** The length of the conflict along the lane centerline. */
80      private final Length length;
81  
82      /** Whether the conflict is a permitted conflict in traffic light control. */
83      private final boolean permitted;
84  
85      /** Distance to upstream traffic light. */
86      private Length trafficLightDistance;
87  
88      /** Maximum maximum search distance. */
89      private Length maxMaxTrafficLightDistance;
90  
91      /////////////////////////////////////////////////////////////////
92      // Properties regarding upstream and downstream GTUs provision //
93      /////////////////////////////////////////////////////////////////
94  
95      /** Root for GTU search. */
96      private final SimpleLaneRecord root;
97  
98      /** Position on the root. */
99      private final Length rootPosition;
100 
101     /** Current upstream GTUs provider. */
102     private AbstractPerceptionIterable<HeadwayGtu, LaneBasedGtu, Integer> upstreamGtus;
103 
104     /** Upstream GTUs update time. */
105     private Time upstreamTime;
106 
107     /** Lanes on which upstream GTUs are found. */
108     private Map<LaneBasedGtu, Lane> upstreamLanes;
109 
110     /** Current downstream GTUs provider. */
111     private AbstractPerceptionIterable<HeadwayGtu, LaneBasedGtu, Integer> downstreamGtus;
112 
113     /** Downstream GTUs update time. */
114     private Time downstreamTime;
115 
116     /** Lanes on which downstream GTUs are found. */
117     private Map<LaneBasedGtu, Lane> downstreamLanes;
118 
119     /** Headway type for the provided GTUs. */
120     private final HeadwayGtuType conflictGtuType = new ConflictGtuType();
121 
122     /** Distance within which upstreamGTUs are provided (is automatically enlarged). */
123     private Length maxUpstreamVisibility = Length.ZERO;
124 
125     /** Distance within which downstreamGTUs are provided (is automatically enlarged). */
126     private Length maxDownstreamVisibility = Length.ZERO;
127 
128     /** Set of upstream GTU that invalidate the iterable when any changes lane. */
129     private Set<LaneBasedGtu> upstreamListening = new LinkedHashSet<>();
130 
131     /** Set of upstream GTU that invalidate the iterable when any changes lane. */
132     private Set<LaneBasedGtu> downstreamListening = new LinkedHashSet<>();
133 
134     /////////////////////////////////////////////////////////////////
135 
136     /**
137      * Construct a new Conflict.
138      * @param lane Lane; lane where this conflict starts
139      * @param longitudinalPosition Length; position of start of conflict on lane
140      * @param length Length; length of the conflict along the lane centerline
141      * @param geometry Polygon2d; geometry of conflict
142      * @param conflictType ConflictType; conflict type, i.e. crossing, merge or split
143      * @param conflictRule ConflictRule; conflict rule, i.e. determines priority, give way, stop or all-stop
144      * @param permitted boolean; whether the conflict is permitted in traffic light control
145      * @throws NetworkException when the position on the lane is out of bounds
146      */
147     @SuppressWarnings("checkstyle:parameternumber")
148     private Conflict(final Lane lane, final Length longitudinalPosition, final Length length, final Polygon2d geometry,
149             final ConflictType conflictType, final ConflictRule conflictRule, final boolean permitted) throws NetworkException
150     {
151         super(UUID.randomUUID().toString(), lane, longitudinalPosition, geometry);
152         this.length = length;
153         this.conflictType = conflictType;
154         this.conflictRule = conflictRule;
155         this.permitted = permitted;
156 
157         // Create conflict end
158         if (conflictType.equals(ConflictType.SPLIT) || conflictType.equals(ConflictType.MERGE))
159         {
160             Length position = conflictType.equals(ConflictType.SPLIT) ? length : lane.getLength();
161             try
162             {
163                 this.end = new ConflictEnd(this, lane, position);
164             }
165             catch (OtsGeometryException exception)
166             {
167                 // does not happen
168                 throw new RuntimeException("Could not create dummy geometry for ConflictEnd.", exception);
169             }
170         }
171         else
172         {
173             this.end = null;
174         }
175 
176         // Lane record for GTU provision
177         this.rootPosition = longitudinalPosition;
178         this.root = new SimpleLaneRecord(lane, this.rootPosition.neg(), null);
179     }
180 
181     /** {@inheritDoc} */
182     @Override
183     protected void init() throws NetworkException
184     {
185         super.init();
186         if (this.end != null)
187         {
188             this.end.init();
189         }
190     }
191 
192     /**
193      * Make sure the conflict can provide the given upstream visibility.
194      * @param visibility Length; visibility to guarantee
195      */
196     private void provideUpstreamVisibility(final Length visibility)
197     {
198         if (visibility.gt(this.maxUpstreamVisibility))
199         {
200             this.maxUpstreamVisibility = visibility;
201             this.upstreamTime = null;
202             this.downstreamTime = null;
203         }
204     }
205 
206     /**
207      * Make sure the conflict can provide the given downstream visibility.
208      * @param visibility Length; visibility to guarantee
209      */
210     private void provideDownstreamVisibility(final Length visibility)
211     {
212         if (visibility.gt(this.maxDownstreamVisibility))
213         {
214             this.maxDownstreamVisibility = visibility;
215             this.upstreamTime = null;
216             this.downstreamTime = null;
217         }
218     }
219 
220     /**
221      * Provides the upstream GTUs.
222      * @param perceivingGtu LaneBasedGtu; perceiving GTU
223      * @param headwayGtuType HeadwayGtuType; headway GTU type to use
224      * @param visibility Length; distance over which GTU's are provided
225      * @return PerceptionIterable&lt;HeadwayGtU&gt;; iterable over the upstream GTUs
226      */
227     public PerceptionCollectable<HeadwayGtu, LaneBasedGtu> getUpstreamGtus(final LaneBasedGtu perceivingGtu,
228             final HeadwayGtuType headwayGtuType, final Length visibility)
229     {
230         provideUpstreamVisibility(visibility);
231         Time time = this.getLane().getLink().getSimulator().getSimulatorAbsTime();
232         if (this.upstreamTime == null || !time.eq(this.upstreamTime))
233         {
234             for (LaneBasedGtu gtu : this.upstreamListening)
235             {
236                 Try.execute(() -> gtu.removeListener(this, LaneBasedGtu.LANE_CHANGE_EVENT), "Unable to unlisten to GTU %s.",
237                         gtu);
238             }
239             this.upstreamListening.clear();
240             // setup a base iterable to provide the GTUs
241             this.upstreamGtus = new UpstreamNeighborsIterable(perceivingGtu, this.root, this.rootPosition,
242                     this.maxUpstreamVisibility, RelativePosition.REFERENCE_POSITION, this.conflictGtuType, RelativeLane.CURRENT)
243             {
244                 /** {@inheritDoc} */
245                 @Override
246                 protected AbstractPerceptionIterable<HeadwayGtu, LaneBasedGtu, Integer>.Entry getNext(
247                         final LaneRecordInterface<?> record, final Length position, final Integer counter) throws GtuException
248                 {
249                     AbstractPerceptionIterable<HeadwayGtu, LaneBasedGtu, Integer>.Entry entry =
250                             super.getNext(record, position, counter);
251                     if (entry != null)
252                     {
253                         Conflict.this.upstreamListening.add(entry.getObject());
254                         Try.execute(() -> entry.getObject().addListener(Conflict.this, LaneBasedGtu.LANE_CHANGE_EVENT),
255                                 "Unable to listen to GTU %s.", entry.getObject());
256                         Conflict.this.upstreamLanes.put(entry.getObject(), record.getLane());
257                     }
258                     return entry;
259                 }
260             };
261             this.upstreamTime = time;
262             this.upstreamLanes = new LinkedHashMap<>();
263         }
264         // return iterable that uses the base iterable
265         return new ConflictGtuIterable(perceivingGtu, headwayGtuType, visibility, false, this.upstreamGtus);
266         // PK does not think this detects GTUs changing lane INTO a lane of concern. Is that bad?
267     }
268 
269     /**
270      * Provides the downstream GTUs.
271      * @param perceivingGtu LaneBasedGtu; perceiving GTU
272      * @param headwayGtuType HeadwayGtuType; headway GTU type to use
273      * @param visibility Length; distance over which GTU's are provided
274      * @return PerceptionIterable&lt;HeadwayGtU&gt;; iterable over the downstream GTUs
275      */
276     public PerceptionCollectable<HeadwayGtu, LaneBasedGtu> getDownstreamGtus(final LaneBasedGtu perceivingGtu,
277             final HeadwayGtuType headwayGtuType, final Length visibility)
278     {
279         provideDownstreamVisibility(visibility);
280         Time time = this.getLane().getLink().getSimulator().getSimulatorAbsTime();
281         if (this.downstreamTime == null || !time.eq(this.downstreamTime))
282         {
283             for (LaneBasedGtu gtu : this.downstreamListening)
284             {
285                 Try.execute(() -> gtu.removeListener(this, LaneBasedGtu.LANE_CHANGE_EVENT), "Unable to unlisten to GTU %s.",
286                         gtu);
287             }
288             this.downstreamListening.clear();
289             // setup a base iterable to provide the GTUs
290             boolean ignoreIfUpstream = false;
291             this.downstreamGtus =
292                     new DownstreamNeighborsIterable(null, this.root, this.rootPosition, this.maxDownstreamVisibility,
293                             RelativePosition.REFERENCE_POSITION, this.conflictGtuType, RelativeLane.CURRENT, ignoreIfUpstream)
294                     {
295                         /** {@inheritDoc} */
296                         @Override
297                         protected AbstractPerceptionIterable<HeadwayGtu, LaneBasedGtu, Integer>.Entry getNext(
298                                 final LaneRecordInterface<?> record, final Length position, final Integer counter)
299                                 throws GtuException
300                         {
301                             AbstractPerceptionIterable<HeadwayGtu, LaneBasedGtu, Integer>.Entry entry =
302                                     super.getNext(record, position, counter);
303                             if (entry != null)
304                             {
305                                 Conflict.this.downstreamListening.add(entry.getObject());
306                                 Try.execute(() -> entry.getObject().addListener(Conflict.this, LaneBasedGtu.LANE_CHANGE_EVENT),
307                                         "Unable to listen to GTU %s.", entry.getObject());
308                                 Conflict.this.downstreamLanes.put(entry.getObject(), record.getLane());
309                             }
310                             return entry;
311                         }
312                     };
313             this.downstreamTime = time;
314             this.downstreamLanes = new LinkedHashMap<>();
315         }
316         // return iterable that uses the base iterable
317         return new ConflictGtuIterable(perceivingGtu, new OverlapHeadway(headwayGtuType), visibility, true,
318                 this.downstreamGtus);
319         // PK does not think this detects GTUs changing lane INTO a lane of concern. Is that bad?
320     }
321 
322     /** {@inheritDoc} */
323     @Override
324     public void notify(final Event event) throws RemoteException
325     {
326         Object[] payload = (Object[]) event.getContent();
327         LaneBasedGtu gtu = (LaneBasedGtu) getLane().getNetwork().getGTU((String) payload[0]);
328         if (this.upstreamListening.contains(gtu))
329         {
330             this.upstreamTime = null;
331         }
332         if (this.downstreamListening.contains(gtu))
333         {
334             this.downstreamTime = null;
335         }
336     }
337 
338     /**
339      * @return conflictType.
340      */
341     public ConflictType getConflictType()
342     {
343         return this.conflictType;
344     }
345 
346     /**
347      * @return conflictRule.
348      */
349     public ConflictRule getConflictRule()
350     {
351         return this.conflictRule;
352     }
353 
354     /**
355      * @return conflictPriority.
356      */
357     public ConflictPriority conflictPriority()
358     {
359         return this.conflictRule.determinePriority(this);
360     }
361     
362     /** {@inheritDoc} */
363     @Override
364     public Polygon2d getGeometry()
365     {
366         return (Polygon2d) super.getGeometry();
367     }
368 
369     /** {@inheritDoc} */
370     @Override
371     public Length getLength()
372     {
373         return this.length;
374     }
375 
376     /**
377      * @return otherConflict.
378      */
379     public Conflict getOtherConflict()
380     {
381         return this.otherConflict;
382     }
383 
384     /**
385      * If permitted, traffic upstream of traffic lights may not be ignored, as these can have green light.
386      * @return permitted.
387      */
388     public boolean isPermitted()
389     {
390         return this.permitted;
391     }
392 
393     /**
394      * Returns the distance to an upstream traffic light.
395      * @param maxDistance Length; maximum distance of traffic light
396      * @return Length; distance to upstream traffic light, infinite if beyond maximum distance
397      */
398     public Length getTrafficLightDistance(final Length maxDistance)
399     {
400         if (this.trafficLightDistance == null)
401         {
402             if (this.maxMaxTrafficLightDistance == null || this.maxMaxTrafficLightDistance.lt(maxDistance))
403             {
404                 this.maxMaxTrafficLightDistance = maxDistance;
405                 boolean downstream = false;
406                 LaneBasedObjectIterable<HeadwayTrafficLight,
407                         TrafficLight> it = new LaneBasedObjectIterable<HeadwayTrafficLight, TrafficLight>(null,
408                                 TrafficLight.class, this.root, getLongitudinalPosition(), downstream, maxDistance,
409                                 RelativePosition.REFERENCE_POSITION, null)
410                         {
411                             /** {@inheritDoc} */
412                             @Override
413                             protected HeadwayTrafficLight perceive(final LaneBasedGtu perceivingGtu, final TrafficLight object,
414                                     final Length distance) throws GtuException, ParameterException
415                             {
416                                 return new HeadwayTrafficLight(object, distance, false);
417                             }
418                         };
419                 if (!it.isEmpty())
420                 {
421                     this.trafficLightDistance = it.first().getDistance();
422                 }
423             }
424         }
425         if (this.trafficLightDistance != null && maxDistance.ge(this.trafficLightDistance))
426         {
427             return this.trafficLightDistance;
428         }
429         return Length.POSITIVE_INFINITY;
430     }
431 
432     /**
433      * Creates a pair of conflicts.
434      * @param conflictType ConflictType; conflict type, i.e. crossing, merge or split
435      * @param conflictRule ConflictRule; conflict rule
436      * @param permitted boolean; whether the conflict is permitted in traffic light control
437      * @param lane1 Lane; lane of conflict 1
438      * @param longitudinalPosition1 Length; longitudinal position of conflict 1
439      * @param length1 Length; {@code Length} of conflict 1
440      * @param geometry1 Polygon2d; geometry of conflict 1
441      * @param lane2 Lane; lane of conflict 2
442      * @param longitudinalPosition2 Length; longitudinal position of conflict 2
443      * @param length2 Length; {@code Length} of conflict 2
444      * @param geometry2 Polygon2d; geometry of conflict 2
445      * @param simulator OtsSimulatorInterface; the simulator for animation and timed events
446      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
447      */
448     @SuppressWarnings("checkstyle:parameternumber")
449     public static void generateConflictPair(final ConflictType conflictType, final ConflictRule conflictRule,
450             final boolean permitted, final Lane lane1, final Length longitudinalPosition1, final Length length1,
451             final Polygon2d geometry1, final Lane lane2, final Length longitudinalPosition2, final Length length2,
452             final Polygon2d geometry2, final OtsSimulatorInterface simulator) throws NetworkException
453     {
454         // lane, longitudinalPosition, length and geometry are checked in AbstractLaneBasedObject
455         Throw.whenNull(conflictType, "Conflict type may not be null.");
456 
457         Conflict conf1 = new Conflict(lane1, longitudinalPosition1, length1, geometry1, conflictType, conflictRule, permitted);
458         conf1.init(); // fire events and register on lane
459         Conflict conf2 = new Conflict(lane2, longitudinalPosition2, length2, geometry2, conflictType, conflictRule, permitted);
460         conf2.init(); // fire events and register on lane
461         conf1.otherConflict = conf2;
462         conf2.otherConflict = conf1;
463     }
464 
465     /** {@inheritDoc} */
466     @Override
467     public double getZ() throws RemoteException
468     {
469         return -0.0001;
470     }
471 
472     /** {@inheritDoc} */
473     @Override
474     public String toString()
475     {
476         return "Conflict [conflictType=" + this.conflictType + ", conflictRule=" + this.conflictRule + "]";
477     }
478 
479     /**
480      * Light-weight lane based object to indicate the end of a conflict. It is used to perceive conflicts when a GTU is on the
481      * conflict area, and hence the conflict lane based object is upstream.
482      * <p>
483      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
484      * <br>
485      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
486      * </p>
487      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
488      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
489      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
490      */
491     public class ConflictEnd extends AbstractLaneBasedObject
492     {
493         /** */
494         private static final long serialVersionUID = 20161214L;
495 
496         /** Conflict at start of conflict area. */
497         private final Conflict conflict;
498 
499         /**
500          * Construct a new ConflictEnd object.
501          * @param conflict Conflict; conflict at start of conflict area
502          * @param lane Lane; lane
503          * @param longitudinalPosition Length; position along the lane of the end of the conflict
504          * @throws NetworkException on network exception
505          * @throws OtsGeometryException does not happen
506          */
507         ConflictEnd(final Conflict conflict, final Lane lane, final Length longitudinalPosition)
508                 throws NetworkException, OtsGeometryException
509         {
510             // FIXME: the OtsLine2d object should be shared by all ConflictEnd objects (removing OtsGeometryException)
511             super(conflict.getId() + "End", lane, longitudinalPosition, new Polygon2d(new Point2d(0, 0), new Point2d(1, 0)));
512             this.conflict = conflict;
513         }
514 
515         /** {@inheritDoc} */
516         @Override
517         public void init() throws NetworkException
518         {
519             // override makes init accessible to conflict
520             super.init();
521         }
522 
523         /**
524          * @return conflict
525          */
526         public final Conflict getConflict()
527         {
528             return this.conflict;
529         }
530 
531         /** {@inheritDoc} */
532         @Override
533         public final String toString()
534         {
535             return "ConflictEnd [conflict=" + this.conflict + "]";
536         }
537     }
538 
539     /**
540      * HeadwayGtu that is returned by base iterators for upstream and downstream GTUs. This class is used with both
541      * {@code UpstreamNeighborsIterable} and {@code DownstreamNeighborsIterable} which work with HeadwayGtu. The role of this
542      * class is however to simply provide the GTU itself such that other specific HeadwayGtu types can be created with it.
543      * Therefore, it extends HeadwayGtuReal which simply wraps the GTU. As the HeadwayGtuReal class has the actual GTU hidden,
544      * this class can provide it.
545      * <p>
546      * FIXME: why not create a getter for the gtu in the super class?
547      * <p>
548      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
549      * <br>
550      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
551      * </p>
552      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
553      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
554      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
555      */
556     private class ConflictGtu extends HeadwayGtuReal
557     {
558         /** */
559         private static final long serialVersionUID = 20180221L;
560 
561         /** Visible pointer to the GTU (which HeadwayGtuReal has not). */
562         private final LaneBasedGtu gtu;
563 
564         /**
565          * Constructor.
566          * @param gtu LaneBasedGtu; gtu
567          * @param overlapFront Length; front overlap
568          * @param overlap Length; overlap
569          * @param overlapRear Length; rear overlap
570          * @throws GtuException on exception
571          */
572         ConflictGtu(final LaneBasedGtu gtu, final Length overlapFront, final Length overlap, final Length overlapRear)
573                 throws GtuException
574         {
575             super(gtu, overlapFront, overlap, overlapRear, true);
576             this.gtu = gtu;
577         }
578 
579         /**
580          * Constructor.
581          * @param gtu LaneBasedGtu; gtu
582          * @param distance Length; distance
583          * @throws GtuException on exception
584          */
585         ConflictGtu(final LaneBasedGtu gtu, final Length distance) throws GtuException
586         {
587             super(gtu, distance, true);
588             this.gtu = gtu;
589         }
590     }
591 
592     /**
593      * HeadwayGtuType that generates ConflictGtu's, for use within the base iterators for upstream and downstream neighbors.
594      * This result is used by secondary iterators (ConflictGtuIterable) to provide the requested specific HeadwatGtuType.
595      * <p>
596      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
597      * <br>
598      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
599      * </p>
600      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
601      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
602      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
603      */
604     private class ConflictGtuType implements HeadwayGtuType
605     {
606         /** Constructor. */
607         ConflictGtuType()
608         {
609             //
610         }
611 
612         /** {@inheritDoc} */
613         @Override
614         public ConflictGtu createHeadwayGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
615                 final Length distance, final boolean downstream) throws GtuException
616         {
617             return new ConflictGtu(perceivedGtu, distance);
618         }
619 
620         /** {@inheritDoc} */
621         @Override
622         public HeadwayGtu createDownstreamGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
623                 final Length distance) throws GtuException, ParameterException
624         {
625             return new ConflictGtu(perceivedGtu, distance); // actually do not change it, called by iterable assuming downstream
626         }
627 
628         /** {@inheritDoc} */
629         @Override
630         public HeadwayGtu createUpstreamGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
631                 final Length distance) throws GtuException, ParameterException
632         {
633             return new ConflictGtu(perceivedGtu, distance); // actually do not change it, called by iterable assuming upstream
634         }
635 
636         /** {@inheritDoc} */
637         @Override
638         public ConflictGtu createParallelGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
639                 final Length overlapFront, final Length overlap, final Length overlapRear) throws GtuException
640         {
641             throw new UnsupportedOperationException("ConflictGtuType is a pass-through type, no actual perception is allowed.");
642         }
643     }
644 
645     /**
646      * HeadwayGtuType that changes a negative headway in to an overlapping headway, by forwarding the request to a wrapped
647      * HeadwayGtuType. This is used for downstream GTUs of the conflict, accounting also for the length of the conflict. Hence,
648      * overlap information concerns the conflict and a downstream GTU (downstream of the start of the conflict).
649      * <p>
650      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
651      * <br>
652      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
653      * </p>
654      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
655      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
656      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
657      */
658     private class OverlapHeadway implements HeadwayGtuType
659     {
660         /** Wrapped headway type. */
661         private HeadwayGtuType wrappedType;
662 
663         /**
664          * Constructor.
665          * @param wrappedType HeadwayGtuType; wrapped headway type
666          */
667         OverlapHeadway(final HeadwayGtuType wrappedType)
668         {
669             this.wrappedType = wrappedType;
670         }
671 
672         /** {@inheritDoc} */
673         @Override
674         public HeadwayGtu createHeadwayGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu, final Length dist,
675                 final boolean downstream) throws GtuException, ParameterException
676         {
677             if (dist.ge(getLength()))
678             {
679                 // GTU fully downstream of the conflict
680                 return this.wrappedType.createHeadwayGtu(perceivingGtu, perceivedGtu, dist.minus(getLength()), downstream);
681             }
682             else
683             {
684                 Length overlapRear = dist;
685                 Length overlap = getLength(); // start with conflict length
686                 Lane lane = downstream ? Conflict.this.downstreamLanes.get(perceivedGtu)
687                         : Conflict.this.upstreamLanes.get(perceivedGtu);
688                 Length overlapFront = dist.plus(perceivedGtu.getProjectedLength(lane)).minus(getLength());
689                 if (overlapFront.lt0())
690                 {
691                     overlap = overlap.plus(overlapFront); // subtract front being before the conflict end
692                 }
693                 if (overlapRear.gt0())
694                 {
695                     overlap = overlap.minus(overlapRear); // subtract rear being past the conflict start
696                 }
697                 return createParallelGtu(perceivingGtu, perceivedGtu, overlapFront, overlap, overlapRear);
698             }
699         }
700 
701         /** {@inheritDoc} */
702         @Override
703         public HeadwayGtu createDownstreamGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
704                 final Length distance) throws GtuException, ParameterException
705         {
706             throw new UnsupportedOperationException("OverlapHeadway is a pass-through type, no actual perception is allowed.");
707         }
708 
709         /** {@inheritDoc} */
710         @Override
711         public HeadwayGtu createUpstreamGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
712                 final Length distance) throws GtuException, ParameterException
713         {
714             throw new UnsupportedOperationException("OverlapHeadway is a pass-through type, no actual perception is allowed.");
715         }
716 
717         /** {@inheritDoc} */
718         @Override
719         public HeadwayGtu createParallelGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
720                 final Length overlapFront, final Length overlap, final Length overlapRear) throws GtuException
721         {
722             return this.wrappedType.createParallelGtu(perceivingGtu, perceivedGtu, overlapFront, overlap, overlapRear);
723         }
724     }
725 
726     /**
727      * Iterable for upstream and downstream GTUs of a conflict, which uses a base iterable.
728      * <p>
729      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
730      * <br>
731      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
732      * </p>
733      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
734      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
735      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
736      */
737     private class ConflictGtuIterable extends AbstractPerceptionReiterable<HeadwayGtu, LaneBasedGtu>
738     {
739         /** HeadwayGtu type. */
740         private final HeadwayGtuType headwayGtuType;
741 
742         /** Guaranteed visibility. */
743         private final Length visibility;
744 
745         /** Downstream (or upstream) neighbors. */
746         private final boolean downstream;
747 
748         /** Base iterator of the base iterable. */
749         private final Iterator<HeadwayGtu> baseIterator;
750 
751         /**
752          * @param perceivingGtu LaneBasedGtu; perceiving GTU
753          * @param headwayGtuType HeadwayGtuType; HeadwayGtu type
754          * @param visibility Length; guaranteed visibility
755          * @param downstream boolean; downstream (or upstream) neighbors
756          * @param base AbstractPerceptionIterable&lt;HeadwayGtu, LaneBasedGtu, Integer&gt;; base iterable from the conflict
757          */
758         ConflictGtuIterable(final LaneBasedGtu perceivingGtu, final HeadwayGtuType headwayGtuType, final Length visibility,
759                 final boolean downstream, final AbstractPerceptionIterable<HeadwayGtu, LaneBasedGtu, Integer> base)
760         {
761             super(perceivingGtu);
762             this.headwayGtuType = headwayGtuType;
763             this.visibility = visibility;
764             this.downstream = downstream;
765             this.baseIterator = base.iterator();
766         }
767 
768         /** {@inheritDoc} */
769         @Override
770         protected Iterator<PrimaryIteratorEntry> primaryIterator()
771         {
772             /**
773              * Iterator that iterates over PrimaryIteratorEntry objects.
774              */
775             class ConflictGtuIterator implements Iterator<PrimaryIteratorEntry>
776             {
777                 /** Next entry. */
778                 private PrimaryIteratorEntry next;
779 
780                 /** {@inheritDoc} */
781                 @Override
782                 public boolean hasNext()
783                 {
784                     if (this.next == null)
785                     {
786                         if (ConflictGtuIterable.this.baseIterator.hasNext())
787                         {
788                             // ConflictGtuIterable is a private class, only used with ConflictGtuType
789                             ConflictGtu gtu = (ConflictGtu) ConflictGtuIterable.this.baseIterator.next();
790                             if (gtu.gtu.getId().equals(getGtu().getId()))
791                             {
792                                 if (ConflictGtuIterable.this.baseIterator.hasNext())
793                                 {
794                                     gtu = (ConflictGtu) ConflictGtuIterable.this.baseIterator.next();
795                                 }
796                                 else
797                                 {
798                                     return false;
799                                 }
800                             }
801                             if (gtu.getDistance() == null || gtu.getDistance().le(ConflictGtuIterable.this.visibility))
802                             {
803                                 this.next = new PrimaryIteratorEntry(gtu.gtu, gtu.getDistance());
804                             }
805                         }
806                     }
807                     return this.next != null;
808                 }
809 
810                 /** {@inheritDoc} */
811                 @Override
812                 public PrimaryIteratorEntry next()
813                 {
814                     if (hasNext())
815                     {
816                         PrimaryIteratorEntry out = this.next;
817                         this.next = null;
818                         return out;
819                     }
820                     throw new NoSuchElementException();
821                 }
822             }
823             return new ConflictGtuIterator();
824         }
825 
826         /** {@inheritDoc} */
827         @Override
828         protected HeadwayGtu perceive(final LaneBasedGtu perceivingGtu, final LaneBasedGtu object, final Length distance)
829                 throws GtuException, ParameterException
830         {
831             return this.headwayGtuType.createHeadwayGtu(perceivingGtu, object, distance, this.downstream);
832         }
833     }
834 
835 }