View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception.categories;
2   
3   import java.util.LinkedHashMap;
4   import java.util.Map;
5   import java.util.function.Supplier;
6   
7   import org.djunits.unit.AccelerationUnit;
8   import org.djunits.value.vdouble.scalar.Acceleration;
9   import org.opentrafficsim.base.TimeStampedObject;
10  import org.opentrafficsim.core.gtu.GTUException;
11  import org.opentrafficsim.core.gtu.perception.AbstractPerceptionCategory;
12  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
13  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
14  
15  /**
16   * Super class for perception categories that use a {@code LaneBasedGTU} and that use lazy evaluation.
17   * <p>
18   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
20   * <p>
21   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 29, 2016 <br>
22   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
24   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
25   */
26  public abstract class LaneBasedAbstractPerceptionCategory extends AbstractPerceptionCategory<LaneBasedGTU, LanePerception>
27  {
28  
29      /** */
30      private static final long serialVersionUID = 20160811L;
31  
32      /**
33       * Maximum deceleration that is used to determine if a vehicle will attempt to stop for a yellow light. <br>
34       * Derived from the report <cite>Onderzoek geeltijden</cite> by Goudappel Coffeng.
35       */
36      public static final Acceleration MAX_YELLOW_DECELERATION = new Acceleration(-2.8, AccelerationUnit.METER_PER_SECOND_2);
37  
38      /**
39       * Maximum deceleration that is used to determine if a vehicle will attempt to stop for a red light. <br>
40       * Not based on any scientific source; sorry.
41       */
42      public static final Acceleration MAX_RED_DECELERATION = new Acceleration(-5, AccelerationUnit.METER_PER_SECOND_2);
43  
44      /** Nested maps to contain context specific keys for given information keys. */
45      private final Map<Object, Map<Object, Object>> contextualKeyMap = new LinkedHashMap<>();
46  
47      /** Map from key, either non-contextual or contextual, and time-stamped object. */
48      private Map<Object, TimeStampedObject<?>> cache = new LinkedHashMap<>();
49  
50      /**
51       * @param perception LanePerception; perception
52       */
53      public LaneBasedAbstractPerceptionCategory(final LanePerception perception)
54      {
55          super(perception);
56      }
57  
58      /**
59       * Returns the cached value for the given key, or computes it if it's absent or not from the current simulation time. The
60       * key represents a type of information, e.g. 'ego speed'.
61       * <p>
62       * This method will compute the information if required. A simple manner to define a {@code Supplier<T>} is to create a
63       * method and invoke it using a lambda expression. For example, suppose we have the method
64       * {@code public Speed getEgoSpeed()} for the tactical planner to use, and {@code private Speed computeEgoSpeed()} for
65       * internal use, then we could use the following line inside {@code getEgoSpeed()}:
66       * 
67       * <pre>
68       * return computeIfAbsent("speedLimit", () -&gt; computeEgoSpeed());
69       * </pre>
70       * 
71       * @param key Object; key defining which information is requested
72       * @param supplier Supplier&lt;T&gt;;
73       * @param <T> value type
74       * @return T; cached or computed value
75       */
76      protected <T> T computeIfAbsent(final Object key, final Supplier<T> supplier)
77      {
78          @SuppressWarnings("unchecked")
79          TimeStampedObject<T> stampedObject = (TimeStampedObject<T>) this.cache.get(key);
80          try
81          {
82              if (stampedObject == null || stampedObject.getTimestamp().lt(getGtu().getSimulator().getSimulatorTime()))
83              {
84                  stampedObject = new TimeStampedObject<>(supplier.get(), getGtu().getSimulator().getSimulatorTime());
85              }
86              return stampedObject.getObject();
87          }
88          catch (GTUException ex)
89          {
90              throw new RuntimeException("Could not obtain time from GTU.");
91          }
92      }
93  
94      /**
95       * Returns the cached value for the given key, or computes it if it's absent or not from the current simulation time. The
96       * key represents a type of information, e.g. 'leading GTUs'. This information is context specific, namely the lane for
97       * which 'leading GTUs' are requested.
98       * <p>
99       * This method will compute the information if required. A simple manner to define a {@code Supplier<T>} is to create a
100      * method and invoke it using a lambda expression. For example, suppose we have the method
101      * {@code public List<HeadwayGTU> getLeaders(Lane)} for the tactical planner to use, and
102      * {@code private List<HeadwayGTU> computeLeaders(Lane)} for internal use, then we could use the following line inside
103      * {@code getLeaders(Lane)}:
104      * 
105      * <pre>
106      * return computeIfAbsent("leaders", () -&gt; computeLeaders(lane))
107      * </pre>
108      * 
109      * @param key Object; key defining which information is requested, it may be contextual if it's context dependent
110      * @param supplier Supplier&lt;T&gt;;
111      * @param context Object; object defining the context, e.g. the lane for which the information is requested
112      * @param <T> value type
113      * @return T; cached or computed value
114      */
115     protected <T> T computeIfAbsent(final Object key, final Supplier<T> supplier, final Object context)
116     {
117         return computeIfAbsent(contextualKey(key, context), supplier);
118     }
119 
120     /**
121      * Returns a key that is unique for the given information key and singled-object context.
122      * @param key Object; information key
123      * @param context Object; context, for example the lane to which the information applies
124      * @return Object; key that is unique for the given information key and singled-object context
125      */
126     private Object contextualKey(final Object key, final Object context)
127     {
128         Map<Object, Object> map = this.contextualKeyMap.computeIfAbsent(key, (k) -> new LinkedHashMap<>());
129         return map.computeIfAbsent(key, (k) -> new Object());
130     }
131 
132     /**
133      * Returns the cached value for the given key, or computes it if it's absent or not from the current simulation time. The
134      * key represents a type of information, e.g. 'neighboring GTUs'. This information is context specific, namely the lane for
135      * which 'neighboring GTUs' are requested, as well as the longitudinal direction.
136      * <p>
137      * It is not advised to use this method at high frequency as it contains slight overhead. Instead, consider defining keys
138      * directly for various contexts. For example:
139      * 
140      * <pre>
141      * private final Object leftLaneLeadersKey = new Object();
142      * 
143      * private final Object leftLaneFollowersKey = new Object();
144      * 
145      * private final Object righttLaneLeadersKey = new Object();
146      * 
147      * private final Object rightLaneFollowersKey = new Object();
148      * </pre>
149      * 
150      * This method will compute the information if required. A simple manner to define a {@code Supplier<T>} is to create a
151      * method and invoke it using a lambda expression. For example, suppose we have the method
152      * {@code public List<HeadwayGTU> getNeighbors(Lane, LongiudinalDirection)} for the tactical planner to use, and
153      * {@code private List<HeadwayGTU> computeNeighbors(Lane, LongiudinalDirection)} for internal use, then we could use the
154      * following line inside {@code getNeighbors(Lane, LongiudinalDirection)}:
155      * 
156      * <pre>
157      * return computeIfAbsent("neighbors", () -&gt; computeNeighbors(lane, longDir))
158      * </pre>
159      * 
160      * @param key Object; key defining which information is requested, it may be contextual if it's context dependent
161      * @param supplier Supplier&lt;T&gt;;
162      * @param context Object...; objects defining the context, e.g. the lane and direction for which the information is
163      *            requested
164      * @param <T> value type
165      * @return T; cached or computed value
166      */
167     protected <T> T computeIfAbsent(final Object key, final Supplier<T> supplier, final Object... context)
168     {
169         return computeIfAbsent(contextualKey(key, context), supplier);
170     }
171 
172     /**
173      * Returns a key that is unique for the given information key and multi-object context.
174      * @param key Object; information key
175      * @param context Object...; context, for example the lane and longitudinal direction to which the information applies
176      * @return Object; key that is unique for the given information key and multi-object context
177      */
178     @SuppressWarnings("unchecked")
179     private Object contextualKey(final Object key, final Object... context)
180     {
181         // get layer of highest level, the key level
182         Map<Object, Object> map = this.contextualKeyMap.computeIfAbsent(key, (k) -> new LinkedHashMap<>());
183         for (int i = 0; i < context.length; i++)
184         {
185             if (i == context.length - 1)
186             {
187                 // deepest layer, i.e. a leaf, we need to return an Object as key
188                 return map.computeIfAbsent(key, (k) -> new Object());
189             }
190             // intermediate layer, we need to obtain the next layer's map
191             map = (Map<Object, Object>) map.computeIfAbsent(context[i], (k) -> new LinkedHashMap<>());
192         }
193         throw new RuntimeException("Unexpected exception while obtaining contextual key for specific perceived info.");
194     }
195 
196 }