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.LaneRecord;
32  import org.opentrafficsim.road.gtu.lane.perception.LaneRecordInterface;
33  import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
34  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
35  import org.opentrafficsim.road.gtu.lane.perception.UpstreamNeighborsIterable;
36  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.HeadwayGtuType;
37  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtu;
38  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtuReal;
39  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayTrafficLight;
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 LaneRecord 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 LaneRecord(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     /**
370      * @return length.
371      */
372     public Length getLength()
373     {
374         return this.length;
375     }
376 
377     /**
378      * @return otherConflict.
379      */
380     public Conflict getOtherConflict()
381     {
382         return this.otherConflict;
383     }
384 
385     /**
386      * If permitted, traffic upstream of traffic lights may not be ignored, as these can have green light.
387      * @return permitted.
388      */
389     public boolean isPermitted()
390     {
391         return this.permitted;
392     }
393 
394     /**
395      * Returns the distance to an upstream traffic light.
396      * @param maxDistance Length; maximum distance of traffic light
397      * @return Length; distance to upstream traffic light, infinite if beyond maximum distance
398      */
399     public Length getTrafficLightDistance(final Length maxDistance)
400     {
401         if (this.trafficLightDistance == null)
402         {
403             if (this.maxMaxTrafficLightDistance == null || this.maxMaxTrafficLightDistance.lt(maxDistance))
404             {
405                 this.maxMaxTrafficLightDistance = maxDistance;
406                 boolean downstream = false;
407                 LaneBasedObjectIterable<HeadwayTrafficLight,
408                         TrafficLight> it = new LaneBasedObjectIterable<HeadwayTrafficLight, TrafficLight>(null,
409                                 TrafficLight.class, this.root, getLongitudinalPosition(), downstream, maxDistance,
410                                 RelativePosition.REFERENCE_POSITION, null)
411                         {
412                             /** {@inheritDoc} */
413                             @Override
414                             protected HeadwayTrafficLight perceive(final LaneBasedGtu perceivingGtu, final TrafficLight object,
415                                     final Length distance) throws GtuException, ParameterException
416                             {
417                                 return new HeadwayTrafficLight(object, distance, false);
418                             }
419                         };
420                 if (!it.isEmpty())
421                 {
422                     this.trafficLightDistance = it.first().getDistance();
423                 }
424             }
425         }
426         if (this.trafficLightDistance != null && maxDistance.ge(this.trafficLightDistance))
427         {
428             return this.trafficLightDistance;
429         }
430         return Length.POSITIVE_INFINITY;
431     }
432 
433     /**
434      * Creates a pair of conflicts.
435      * @param conflictType ConflictType; conflict type, i.e. crossing, merge or split
436      * @param conflictRule ConflictRule; conflict rule
437      * @param permitted boolean; whether the conflict is permitted in traffic light control
438      * @param lane1 Lane; lane of conflict 1
439      * @param longitudinalPosition1 Length; longitudinal position of conflict 1
440      * @param length1 Length; {@code Length} of conflict 1
441      * @param geometry1 Polygon2d; geometry of conflict 1
442      * @param lane2 Lane; lane of conflict 2
443      * @param longitudinalPosition2 Length; longitudinal position of conflict 2
444      * @param length2 Length; {@code Length} of conflict 2
445      * @param geometry2 Polygon2d; geometry of conflict 2
446      * @param simulator OtsSimulatorInterface; the simulator for animation and timed events
447      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
448      */
449     @SuppressWarnings("checkstyle:parameternumber")
450     public static void generateConflictPair(final ConflictType conflictType, final ConflictRule conflictRule,
451             final boolean permitted, final Lane lane1, final Length longitudinalPosition1, final Length length1,
452             final Polygon2d geometry1, final Lane lane2, final Length longitudinalPosition2, final Length length2,
453             final Polygon2d geometry2, final OtsSimulatorInterface simulator) throws NetworkException
454     {
455         // lane, longitudinalPosition, length and geometry are checked in AbstractLaneBasedObject
456         Throw.whenNull(conflictType, "Conflict type may not be null.");
457 
458         Conflict conf1 = new Conflict(lane1, longitudinalPosition1, length1, geometry1, conflictType, conflictRule, permitted);
459         conf1.init(); // fire events and register on lane
460         Conflict conf2 = new Conflict(lane2, longitudinalPosition2, length2, geometry2, conflictType, conflictRule, permitted);
461         conf2.init(); // fire events and register on lane
462         conf1.otherConflict = conf2;
463         conf2.otherConflict = conf1;
464     }
465 
466     /** {@inheritDoc} */
467     @Override
468     public double getZ() throws RemoteException
469     {
470         return -0.0001;
471     }
472 
473     /** {@inheritDoc} */
474     @Override
475     public String toString()
476     {
477         return "Conflict [conflictType=" + this.conflictType + ", conflictRule=" + this.conflictRule + "]";
478     }
479 
480     /**
481      * Light-weight lane based object to indicate the end of a conflict. It is used to perceive conflicts when a GTU is on the
482      * conflict area, and hence the conflict lane based object is upstream.
483      * <p>
484      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
485      * <br>
486      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
487      * </p>
488      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
489      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
490      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
491      */
492     public class ConflictEnd extends AbstractLaneBasedObject
493     {
494         /** */
495         private static final long serialVersionUID = 20161214L;
496 
497         /** Conflict at start of conflict area. */
498         private final Conflict conflict;
499 
500         /**
501          * Construct a new ConflictEnd object.
502          * @param conflict Conflict; conflict at start of conflict area
503          * @param lane Lane; lane
504          * @param longitudinalPosition Length; position along the lane of the end of the conflict
505          * @throws NetworkException on network exception
506          * @throws OtsGeometryException does not happen
507          */
508         ConflictEnd(final Conflict conflict, final Lane lane, final Length longitudinalPosition)
509                 throws NetworkException, OtsGeometryException
510         {
511             // FIXME: the OtsLine2d object should be shared by all ConflictEnd objects (removing OtsGeometryException)
512             super(conflict.getId() + "End", lane, longitudinalPosition, new Polygon2d(new Point2d(0, 0), new Point2d(1, 0)));
513             this.conflict = conflict;
514         }
515 
516         /** {@inheritDoc} */
517         @Override
518         public void init() throws NetworkException
519         {
520             // override makes init accessible to conflict
521             super.init();
522         }
523 
524         /**
525          * @return conflict
526          */
527         public final Conflict getConflict()
528         {
529             return this.conflict;
530         }
531 
532         /** {@inheritDoc} */
533         @Override
534         public final String toString()
535         {
536             return "ConflictEnd [conflict=" + this.conflict + "]";
537         }
538     }
539 
540     /**
541      * HeadwayGtu that is returned by base iterators for upstream and downstream GTUs. This class is used with both
542      * {@code UpstreamNeighborsIterable} and {@code DownstreamNeighborsIterable} which work with HeadwayGtu. The role of this
543      * class is however to simply provide the GTU itself such that other specific HeadwayGtu types can be created with it.
544      * Therefore, it extends HeadwayGtuReal which simply wraps the GTU. As the HeadwayGtuReal class has the actual GTU hidden,
545      * this class can provide it.
546      * <p>
547      * FIXME: why not create a getter for the gtu in the super class?
548      * <p>
549      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
550      * <br>
551      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
552      * </p>
553      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
554      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
555      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
556      */
557     private class ConflictGtu extends HeadwayGtuReal
558     {
559         /** */
560         private static final long serialVersionUID = 20180221L;
561 
562         /** Visible pointer to the GTU (which HeadwayGtuReal has not). */
563         private final LaneBasedGtu gtu;
564 
565         /**
566          * Constructor.
567          * @param gtu LaneBasedGtu; gtu
568          * @param overlapFront Length; front overlap
569          * @param overlap Length; overlap
570          * @param overlapRear Length; rear overlap
571          * @throws GtuException on exception
572          */
573         ConflictGtu(final LaneBasedGtu gtu, final Length overlapFront, final Length overlap, final Length overlapRear)
574                 throws GtuException
575         {
576             super(gtu, overlapFront, overlap, overlapRear, true);
577             this.gtu = gtu;
578         }
579 
580         /**
581          * Constructor.
582          * @param gtu LaneBasedGtu; gtu
583          * @param distance Length; distance
584          * @throws GtuException on exception
585          */
586         ConflictGtu(final LaneBasedGtu gtu, final Length distance) throws GtuException
587         {
588             super(gtu, distance, true);
589             this.gtu = gtu;
590         }
591     }
592 
593     /**
594      * HeadwayGtuType that generates ConflictGtu's, for use within the base iterators for upstream and downstream neighbors.
595      * This result is used by secondary iterators (ConflictGtuIterable) to provide the requested specific HeadwatGtuType.
596      * <p>
597      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
598      * <br>
599      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
600      * </p>
601      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
602      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
603      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
604      */
605     private class ConflictGtuType implements HeadwayGtuType
606     {
607         /** Constructor. */
608         ConflictGtuType()
609         {
610             //
611         }
612 
613         /** {@inheritDoc} */
614         @Override
615         public ConflictGtu createHeadwayGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
616                 final Length distance, final boolean downstream) throws GtuException
617         {
618             return new ConflictGtu(perceivedGtu, distance);
619         }
620 
621         /** {@inheritDoc} */
622         @Override
623         public HeadwayGtu createDownstreamGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
624                 final Length distance) throws GtuException, ParameterException
625         {
626             return new ConflictGtu(perceivedGtu, distance); // actually do not change it, called by iterable assuming downstream
627         }
628 
629         /** {@inheritDoc} */
630         @Override
631         public HeadwayGtu createUpstreamGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
632                 final Length distance) throws GtuException, ParameterException
633         {
634             return new ConflictGtu(perceivedGtu, distance); // actually do not change it, called by iterable assuming upstream
635         }
636 
637         /** {@inheritDoc} */
638         @Override
639         public ConflictGtu createParallelGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
640                 final Length overlapFront, final Length overlap, final Length overlapRear) throws GtuException
641         {
642             throw new UnsupportedOperationException("ConflictGtuType is a pass-through type, no actual perception is allowed.");
643         }
644     }
645 
646     /**
647      * HeadwayGtuType that changes a negative headway in to an overlapping headway, by forwarding the request to a wrapped
648      * HeadwayGtuType. This is used for downstream GTUs of the conflict, accounting also for the length of the conflict. Hence,
649      * overlap information concerns the conflict and a downstream GTU (downstream of the start of the conflict).
650      * <p>
651      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
652      * <br>
653      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
654      * </p>
655      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
656      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
657      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
658      */
659     private class OverlapHeadway implements HeadwayGtuType
660     {
661         /** Wrapped headway type. */
662         private HeadwayGtuType wrappedType;
663 
664         /**
665          * Constructor.
666          * @param wrappedType HeadwayGtuType; wrapped headway type
667          */
668         OverlapHeadway(final HeadwayGtuType wrappedType)
669         {
670             this.wrappedType = wrappedType;
671         }
672 
673         /** {@inheritDoc} */
674         @Override
675         public HeadwayGtu createHeadwayGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu, final Length dist,
676                 final boolean downstream) throws GtuException, ParameterException
677         {
678             if (dist.ge(getLength()))
679             {
680                 // GTU fully downstream of the conflict
681                 return this.wrappedType.createHeadwayGtu(perceivingGtu, perceivedGtu, dist.minus(getLength()), downstream);
682             }
683             else
684             {
685                 Length overlapRear = dist;
686                 Length overlap = getLength(); // start with conflict length
687                 Lane lane = downstream ? Conflict.this.downstreamLanes.get(perceivedGtu)
688                         : Conflict.this.upstreamLanes.get(perceivedGtu);
689                 Length overlapFront = dist.plus(perceivedGtu.getProjectedLength(lane)).minus(getLength());
690                 if (overlapFront.lt0())
691                 {
692                     overlap = overlap.plus(overlapFront); // subtract front being before the conflict end
693                 }
694                 if (overlapRear.gt0())
695                 {
696                     overlap = overlap.minus(overlapRear); // subtract rear being past the conflict start
697                 }
698                 return createParallelGtu(perceivingGtu, perceivedGtu, overlapFront, overlap, overlapRear);
699             }
700         }
701 
702         /** {@inheritDoc} */
703         @Override
704         public HeadwayGtu createDownstreamGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
705                 final Length distance) throws GtuException, ParameterException
706         {
707             throw new UnsupportedOperationException("OverlapHeadway is a pass-through type, no actual perception is allowed.");
708         }
709 
710         /** {@inheritDoc} */
711         @Override
712         public HeadwayGtu createUpstreamGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
713                 final Length distance) throws GtuException, ParameterException
714         {
715             throw new UnsupportedOperationException("OverlapHeadway is a pass-through type, no actual perception is allowed.");
716         }
717 
718         /** {@inheritDoc} */
719         @Override
720         public HeadwayGtu createParallelGtu(final LaneBasedGtu perceivingGtu, final LaneBasedGtu perceivedGtu,
721                 final Length overlapFront, final Length overlap, final Length overlapRear) throws GtuException
722         {
723             return this.wrappedType.createParallelGtu(perceivingGtu, perceivedGtu, overlapFront, overlap, overlapRear);
724         }
725     }
726 
727     /**
728      * Iterable for upstream and downstream GTUs of a conflict, which uses a base iterable.
729      * <p>
730      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
731      * <br>
732      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
733      * </p>
734      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
735      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
736      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
737      */
738     private class ConflictGtuIterable extends AbstractPerceptionReiterable<HeadwayGtu, LaneBasedGtu>
739     {
740         /** HeadwayGtu type. */
741         private final HeadwayGtuType headwayGtuType;
742 
743         /** Guaranteed visibility. */
744         private final Length visibility;
745 
746         /** Downstream (or upstream) neighbors. */
747         private final boolean downstream;
748 
749         /** Base iterator of the base iterable. */
750         private final Iterator<HeadwayGtu> baseIterator;
751 
752         /**
753          * @param perceivingGtu LaneBasedGtu; perceiving GTU
754          * @param headwayGtuType HeadwayGtuType; HeadwayGtu type
755          * @param visibility Length; guaranteed visibility
756          * @param downstream boolean; downstream (or upstream) neighbors
757          * @param base AbstractPerceptionIterable&lt;HeadwayGtu, LaneBasedGtu, Integer&gt;; base iterable from the conflict
758          */
759         ConflictGtuIterable(final LaneBasedGtu perceivingGtu, final HeadwayGtuType headwayGtuType, final Length visibility,
760                 final boolean downstream, final AbstractPerceptionIterable<HeadwayGtu, LaneBasedGtu, Integer> base)
761         {
762             super(perceivingGtu);
763             this.headwayGtuType = headwayGtuType;
764             this.visibility = visibility;
765             this.downstream = downstream;
766             this.baseIterator = base.iterator();
767         }
768 
769         /** {@inheritDoc} */
770         @Override
771         protected Iterator<PrimaryIteratorEntry> primaryIterator()
772         {
773             /**
774              * Iterator that iterates over PrimaryIteratorEntry objects.
775              */
776             class ConflictGtuIterator implements Iterator<PrimaryIteratorEntry>
777             {
778                 /** Next entry. */
779                 private PrimaryIteratorEntry next;
780 
781                 /** {@inheritDoc} */
782                 @Override
783                 public boolean hasNext()
784                 {
785                     if (this.next == null)
786                     {
787                         if (ConflictGtuIterable.this.baseIterator.hasNext())
788                         {
789                             // ConflictGtuIterable is a private class, only used with ConflictGtuType
790                             ConflictGtu gtu = (ConflictGtu) ConflictGtuIterable.this.baseIterator.next();
791                             if (gtu.gtu.getId().equals(getGtu().getId()))
792                             {
793                                 if (ConflictGtuIterable.this.baseIterator.hasNext())
794                                 {
795                                     gtu = (ConflictGtu) ConflictGtuIterable.this.baseIterator.next();
796                                 }
797                                 else
798                                 {
799                                     return false;
800                                 }
801                             }
802                             if (gtu.getDistance() == null || gtu.getDistance().le(ConflictGtuIterable.this.visibility))
803                             {
804                                 this.next = new PrimaryIteratorEntry(gtu.gtu, gtu.getDistance());
805                             }
806                         }
807                     }
808                     return this.next != null;
809                 }
810 
811                 /** {@inheritDoc} */
812                 @Override
813                 public PrimaryIteratorEntry next()
814                 {
815                     if (hasNext())
816                     {
817                         PrimaryIteratorEntry out = this.next;
818                         this.next = null;
819                         return out;
820                     }
821                     throw new NoSuchElementException();
822                 }
823             }
824             return new ConflictGtuIterator();
825         }
826 
827         /** {@inheritDoc} */
828         @Override
829         protected HeadwayGtu perceive(final LaneBasedGtu perceivingGtu, final LaneBasedGtu object, final Length distance)
830                 throws GtuException, ParameterException
831         {
832             return this.headwayGtuType.createHeadwayGtu(perceivingGtu, object, distance, this.downstream);
833         }
834     }
835 
836 }