LaneBasedAbstractPerceptionCategory.java
package org.opentrafficsim.road.gtu.lane.perception.categories;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.djunits.unit.AccelerationUnit;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.opentrafficsim.base.TimeStampedObject;
import org.opentrafficsim.core.gtu.GTUException;
import org.opentrafficsim.core.gtu.perception.AbstractPerceptionCategory;
import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
/**
* Super class for perception categories that use a {@code LaneBasedGTU} and that use lazy evaluation.
* <p>
* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
* <p>
* @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 29, 2016 <br>
* @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
* @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
*/
public abstract class LaneBasedAbstractPerceptionCategory extends AbstractPerceptionCategory<LaneBasedGTU, LanePerception>
{
/** */
private static final long serialVersionUID = 20160811L;
/**
* Maximum deceleration that is used to determine if a vehicle will attempt to stop for a yellow light. <br>
* Derived from the report <cite>Onderzoek geeltijden</cite> by Goudappel Coffeng.
*/
public static final Acceleration MAX_YELLOW_DECELERATION = new Acceleration(-2.8, AccelerationUnit.METER_PER_SECOND_2);
/**
* Maximum deceleration that is used to determine if a vehicle will attempt to stop for a red light. <br>
* Not based on any scientific source; sorry.
*/
public static final Acceleration MAX_RED_DECELERATION = new Acceleration(-5, AccelerationUnit.METER_PER_SECOND_2);
/** Nested maps to contain context specific keys for given information keys. */
private final Map<Object, Map<Object, Object>> contextualKeyMap = new LinkedHashMap<>();
/** Map from key, either non-contextual or contextual, and time-stamped object. */
private Map<Object, TimeStampedObject<?>> cache = new LinkedHashMap<>();
/**
* @param perception LanePerception; perception
*/
public LaneBasedAbstractPerceptionCategory(final LanePerception perception)
{
super(perception);
}
/**
* Returns the cached value for the given key, or computes it if it's absent or not from the current simulation time. The
* key represents a type of information, e.g. 'ego speed'.
* <p>
* This method will compute the information if required. A simple manner to define a {@code Supplier<T>} is to create a
* method and invoke it using a lambda expression. For example, suppose we have the method
* {@code public Speed getEgoSpeed()} for the tactical planner to use, and {@code private Speed computeEgoSpeed()} for
* internal use, then we could use the following line inside {@code getEgoSpeed()}:
*
* <pre>
* return computeIfAbsent("speedLimit", () -> computeEgoSpeed());
* </pre>
*
* @param key Object; key defining which information is requested
* @param supplier Supplier<T>;
* @param <T> value type
* @return T; cached or computed value
*/
protected <T> T computeIfAbsent(final Object key, final Supplier<T> supplier)
{
@SuppressWarnings("unchecked")
TimeStampedObject<T> stampedObject = (TimeStampedObject<T>) this.cache.get(key);
try
{
if (stampedObject == null || stampedObject.getTimestamp().lt(getGtu().getSimulator().getSimulatorTime()))
{
stampedObject = new TimeStampedObject<>(supplier.get(), getGtu().getSimulator().getSimulatorTime());
}
return stampedObject.getObject();
}
catch (GTUException ex)
{
throw new RuntimeException("Could not obtain time from GTU.");
}
}
/**
* Returns the cached value for the given key, or computes it if it's absent or not from the current simulation time. The
* key represents a type of information, e.g. 'leading GTUs'. This information is context specific, namely the lane for
* which 'leading GTUs' are requested.
* <p>
* This method will compute the information if required. A simple manner to define a {@code Supplier<T>} is to create a
* method and invoke it using a lambda expression. For example, suppose we have the method
* {@code public List<HeadwayGTU> getLeaders(Lane)} for the tactical planner to use, and
* {@code private List<HeadwayGTU> computeLeaders(Lane)} for internal use, then we could use the following line inside
* {@code getLeaders(Lane)}:
*
* <pre>
* return computeIfAbsent("leaders", () -> computeLeaders(lane))
* </pre>
*
* @param key Object; key defining which information is requested, it may be contextual if it's context dependent
* @param supplier Supplier<T>;
* @param context Object; object defining the context, e.g. the lane for which the information is requested
* @param <T> value type
* @return T; cached or computed value
*/
protected <T> T computeIfAbsent(final Object key, final Supplier<T> supplier, final Object context)
{
return computeIfAbsent(contextualKey(key, context), supplier);
}
/**
* Returns a key that is unique for the given information key and singled-object context.
* @param key Object; information key
* @param context Object; context, for example the lane to which the information applies
* @return Object; key that is unique for the given information key and singled-object context
*/
private Object contextualKey(final Object key, final Object context)
{
Map<Object, Object> map = this.contextualKeyMap.computeIfAbsent(key, (k) -> new LinkedHashMap<>());
return map.computeIfAbsent(key, (k) -> new Object());
}
/**
* Returns the cached value for the given key, or computes it if it's absent or not from the current simulation time. The
* key represents a type of information, e.g. 'neighboring GTUs'. This information is context specific, namely the lane for
* which 'neighboring GTUs' are requested, as well as the longitudinal direction.
* <p>
* It is not advised to use this method at high frequency as it contains slight overhead. Instead, consider defining keys
* directly for various contexts. For example:
*
* <pre>
* private final Object leftLaneLeadersKey = new Object();
*
* private final Object leftLaneFollowersKey = new Object();
*
* private final Object righttLaneLeadersKey = new Object();
*
* private final Object rightLaneFollowersKey = new Object();
* </pre>
*
* This method will compute the information if required. A simple manner to define a {@code Supplier<T>} is to create a
* method and invoke it using a lambda expression. For example, suppose we have the method
* {@code public List<HeadwayGTU> getNeighbors(Lane, LongiudinalDirection)} for the tactical planner to use, and
* {@code private List<HeadwayGTU> computeNeighbors(Lane, LongiudinalDirection)} for internal use, then we could use the
* following line inside {@code getNeighbors(Lane, LongiudinalDirection)}:
*
* <pre>
* return computeIfAbsent("neighbors", () -> computeNeighbors(lane, longDir))
* </pre>
*
* @param key Object; key defining which information is requested, it may be contextual if it's context dependent
* @param supplier Supplier<T>;
* @param context Object...; objects defining the context, e.g. the lane and direction for which the information is
* requested
* @param <T> value type
* @return T; cached or computed value
*/
protected <T> T computeIfAbsent(final Object key, final Supplier<T> supplier, final Object... context)
{
return computeIfAbsent(contextualKey(key, context), supplier);
}
/**
* Returns a key that is unique for the given information key and multi-object context.
* @param key Object; information key
* @param context Object...; context, for example the lane and longitudinal direction to which the information applies
* @return Object; key that is unique for the given information key and multi-object context
*/
@SuppressWarnings("unchecked")
private Object contextualKey(final Object key, final Object... context)
{
// get layer of highest level, the key level
Map<Object, Object> map = this.contextualKeyMap.computeIfAbsent(key, (k) -> new LinkedHashMap<>());
for (int i = 0; i < context.length; i++)
{
if (i == context.length - 1)
{
// deepest layer, i.e. a leaf, we need to return an Object as key
return map.computeIfAbsent(key, (k) -> new Object());
}
// intermediate layer, we need to obtain the next layer's map
map = (Map<Object, Object>) map.computeIfAbsent(context[i], (k) -> new LinkedHashMap<>());
}
throw new RuntimeException("Unexpected exception while obtaining contextual key for specific perceived info.");
}
}