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", () -> computeEgoSpeed()); 69 * </pre> 70 * 71 * @param key Object; key defining which information is requested 72 * @param supplier Supplier<T>; 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", () -> 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<T>; 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", () -> 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<T>; 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 }