View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception.mental;
2   
3   import java.util.Iterator;
4   import java.util.LinkedHashMap;
5   import java.util.LinkedHashSet;
6   import java.util.Map;
7   import java.util.OptionalDouble;
8   import java.util.Set;
9   import java.util.function.BiFunction;
10  
11  import org.djunits.value.vdouble.scalar.Duration;
12  import org.djunits.value.vdouble.scalar.Length;
13  import org.djutils.event.Event;
14  import org.djutils.event.EventListener;
15  import org.opentrafficsim.base.parameters.ParameterException;
16  import org.opentrafficsim.core.gtu.RelativePosition;
17  import org.opentrafficsim.core.network.LateralDirectionality;
18  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
19  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
20  import org.opentrafficsim.road.gtu.lane.perception.structure.LaneStructure;
21  import org.opentrafficsim.road.gtu.lane.perception.structure.NavigatingIterable.Entry;
22  import org.opentrafficsim.road.network.lane.object.RoadSideDistraction;
23  
24  /**
25   * This class perceives all distractions. It stores information on lane, odometer and task demand per distraction. For
26   * distraction objects that are behind, the information on odometer allows active distraction for some distance beyond the
27   * distraction object. This class listens to lane changes to update the relevant lane information. An instance of this class
28   * should be shared among different instances of tasks which each can request the total level of distraction given the filter
29   * each supplies to the {@link #getDistraction(BiFunction)} method.
30   * <p>
31   * Copyright (c) 2026-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
32   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
33   * </p>
34   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
35   */
36  public class DistractionField implements EventListener
37  {
38  
39      /** GTU. */
40      private final LaneBasedGtu gtu;
41  
42      /** Last time distractions were updated. */
43      private Duration updateTime = null;
44  
45      /** Odometer values at distraction. */
46      private final Map<RoadSideDistraction, Double> odos = new LinkedHashMap<>();
47  
48      /** Task demand per distraction. */
49      private final Map<RoadSideDistraction, Double> taskDemands = new LinkedHashMap<>();
50  
51      /** Lanes and the applicable distractions. */
52      private Map<RelativeLane, Set<RoadSideDistraction>> lanes = new LinkedHashMap<>();
53  
54      /**
55       * Constructor.
56       * @param gtu GTU
57       */
58      public DistractionField(final LaneBasedGtu gtu)
59      {
60          this.gtu = gtu;
61          gtu.addListener(this, LaneBasedGtu.LANE_CHANGE_EVENT);
62      }
63  
64      @Override
65      public void notify(final Event event)
66      {
67          LateralDirectionality dir = LateralDirectionality.valueOf((String) ((Object[]) event.getContent())[1]);
68          RelativeLane shift = new RelativeLane(dir, 1);
69          Map<RelativeLane, Set<RoadSideDistraction>> newMap = new LinkedHashMap<>();
70          this.lanes.entrySet().stream().forEach((e) -> newMap.put(e.getKey().add(shift), e.getValue()));
71          this.lanes = newMap;
72      }
73  
74      /**
75       * Returns the level of distraction for the given direction.
76       * @param filter filter to retain relevant distractions
77       * @return level of distraction for the given direction
78       * @throws ParameterException if parameter for lane structure is missing
79       */
80      public double getDistraction(final BiFunction<RelativeLane, RoadSideDistraction, Boolean> filter) throws ParameterException
81      {
82          perceiveDistractions();
83          double taskDemand = 0.0;
84          for (RelativeLane lane : this.lanes.keySet())
85          {
86              for (RoadSideDistraction distraction : this.lanes.get(lane))
87              {
88                  if (filter.apply(lane, distraction))
89                  {
90                      taskDemand += this.taskDemands.get(distraction);
91                  }
92              }
93          }
94          return taskDemand;
95      }
96  
97      /**
98       * Caches odometer, lane and task demand for all distractions. Distractions are remembered when passed by their odometer.
99       * Once the distraction results in a no-value task demand, it is removed as it is too far behind.
100      * @throws ParameterException if parameter for lane structure is missing
101      */
102     private void perceiveDistractions() throws ParameterException
103     {
104         if (this.updateTime != null && this.gtu.getSimulator().getSimulatorTime().le(this.updateTime))
105         {
106             return;
107         }
108         this.updateTime = this.gtu.getSimulator().getSimulatorTime();
109 
110         // update odometer values and set lanes of all downstream distractions
111         LaneStructure laneStructure = this.gtu.getTacticalPlanner().getPerception().getLaneStructure();
112         double odo = this.gtu.getOdometer().si;
113         for (RelativeLane lane : laneStructure.getRootCrossSection())
114         {
115             for (Entry<RoadSideDistraction> distraction : laneStructure.getDownstreamObjects(lane, RoadSideDistraction.class,
116                     RelativePosition.FRONT, false))
117             {
118                 this.odos.put(distraction.object(), odo + distraction.distance().si);
119                 this.lanes.computeIfAbsent(lane, (l) -> new LinkedHashSet<>()).add(distraction.object());
120             }
121         }
122 
123         // calculate distraction task demand and remove those without a value (indicating it is to far behind)
124         Iterator<RoadSideDistraction> distractionIterator = this.odos.keySet().iterator();
125         while (distractionIterator.hasNext())
126         {
127             RoadSideDistraction distraction = distractionIterator.next();
128             OptionalDouble td = distraction.getDistraction(Length.ofSI(odo - this.odos.get(distraction)));
129             if (td.isEmpty())
130             {
131                 distractionIterator.remove();
132                 this.taskDemands.remove(distraction);
133                 this.lanes.values().stream().forEach((s) -> s.remove(distraction));
134             }
135             else
136             {
137                 this.taskDemands.put(distraction, td.getAsDouble());
138             }
139         }
140 
141         // remove all lanes that no longer have any distraction
142         Iterator<Set<RoadSideDistraction>> laneIterator = this.lanes.values().iterator();
143         while (laneIterator.hasNext())
144         {
145             if (laneIterator.next().isEmpty())
146             {
147                 laneIterator.remove();
148             }
149         }
150     }
151 
152 }