View Javadoc
1   package org.opentrafficsim.road.gtu.tactical;
2   
3   import java.util.LinkedHashMap;
4   import java.util.Map;
5   import java.util.Map.Entry;
6   import java.util.NavigableMap;
7   import java.util.NoSuchElementException;
8   import java.util.Optional;
9   import java.util.TreeMap;
10  
11  import org.djunits.value.vdouble.scalar.Acceleration;
12  import org.djunits.value.vdouble.scalar.Duration;
13  import org.djunits.value.vdouble.scalar.Length;
14  import org.djunits.value.vdouble.scalar.Speed;
15  import org.djutils.exceptions.Throw;
16  import org.opentrafficsim.base.DistancedObject;
17  import org.opentrafficsim.base.parameters.ParameterException;
18  import org.opentrafficsim.base.parameters.Parameters;
19  import org.opentrafficsim.core.gtu.GtuType;
20  import org.opentrafficsim.core.gtu.perception.EgoPerception;
21  import org.opentrafficsim.core.network.LateralDirectionality;
22  import org.opentrafficsim.core.network.route.Route;
23  import org.opentrafficsim.road.gtu.LaneBasedGtu;
24  import org.opentrafficsim.road.gtu.perception.LanePerception;
25  import org.opentrafficsim.road.gtu.perception.RelativeLane;
26  import org.opentrafficsim.road.gtu.perception.categories.InfrastructurePerception;
27  import org.opentrafficsim.road.gtu.perception.categories.neighbors.NeighborsPerception;
28  import org.opentrafficsim.road.gtu.tactical.following.CarFollowingModel;
29  import org.opentrafficsim.road.network.LanePosition;
30  import org.opentrafficsim.road.network.speed.SpeedLimits;
31  
32  /**
33   * Wraps a {@link LaneBasedGtu} and provides bundled and easy-access information that tactical models and their components need
34   * regarding the ego-vehicle. This information may be cached, such that different components do not need to repeat expensive
35   * steps to obtain certain information. Because of the caching, this is a throw-away class that should only have a lifetime
36   * equal to a single model execution.
37   * <p>
38   * Copyright (c) 2026-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
39   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
40   * </p>
41   * @author Wouter Schakel
42   */
43  public class TacticalContextEgo implements TacticalContext
44  {
45  
46      /** GTU. */
47      private final LaneBasedGtu gtu;
48  
49      /** Perception. */
50      private LanePerception perception;
51  
52      /** Route. */
53      private Optional<Route> route;
54  
55      /** Desired speed from car-following model. */
56      private Speed desiredSpeed;
57  
58      /** Car-following acceleration. */
59      private Acceleration carFollowingAcceleration;
60  
61      /** Intents. */
62      private Map<Class<?>, NavigableMap<Length, Object>> intents = new LinkedHashMap<>();
63  
64      /** Parameters. */
65      private Parameters parameters;
66  
67      /** Car-following model. */
68      private CarFollowingModel carFollowingModel;
69  
70      /** Speed limits. */
71      private SpeedLimits speedLimits;
72  
73      /** Ego length. */
74      private Length length;
75  
76      /** Ego width. */
77      private Length width;
78  
79      /** Ego speed. */
80      private Speed speed;
81  
82      /** Ego maximum speed. */
83      private Speed maxSpeed;
84  
85      /** Ego acceleration. */
86      private Acceleration acceleration;
87  
88      /**
89       * Constructor.
90       * @param gtu GTU
91       */
92      public TacticalContextEgo(final LaneBasedGtu gtu)
93      {
94          this.gtu = gtu;
95      }
96  
97      /**
98       * Returns the GTU. This is tagged as unsafe because models should not typically interfere with the simulation structure.
99       * Still, sometimes information or commands are required outside of the tactical context. This should only be done with care
100      * and full understanding of what is being done.
101      * @return GTU
102      */
103     public LaneBasedGtu getUnsafeGtu()
104     {
105         return this.gtu;
106     }
107 
108     /**
109      * Returns the perception.
110      * @return perception
111      */
112     public LanePerception getPerception()
113     {
114         if (this.perception == null)
115         {
116             this.perception = this.gtu.getTacticalPlanner().getPerception();
117         }
118         return this.perception;
119     }
120 
121     /**
122      * Returns the current time.
123      * @return current time
124      */
125     public Duration getTime()
126     {
127         return getUnsafeGtu().getSimulator().getSimulatorTime();
128     }
129 
130     /**
131      * Returns the car-following acceleration from the car-following model and leaders perceived by the neighbor category.
132      * @return car-following acceleration from the car-following model
133      * @throws NoSuchElementException when neighbors perception is not present
134      * @throws ParameterException if a parameter is not present
135      */
136     public Acceleration getCarFollowingAcceleration() throws ParameterException
137     {
138         if (this.carFollowingAcceleration == null)
139         {
140             var leaders = getPerception().getPerceptionCategoryOptional(NeighborsPerception.class)
141                     .orElseThrow(() -> new NoSuchElementException(
142                             "To obtain car-following acceleration a neighbors perception category is required."))
143                     .getLeaders(RelativeLane.CURRENT);
144             this.carFollowingAcceleration = getCarFollowingModel().followingAcceleration(getParameters(), getSpeed(),
145                     getSpeedLimits(), getMaximumSpeed(), leaders);
146         }
147         return this.carFollowingAcceleration;
148     }
149 
150     /**
151      * Returns the current position.
152      * @return current position
153      */
154     public LanePosition getPosition()
155     {
156         return getUnsafeGtu().getPosition();
157     }
158 
159     /**
160      * Add value for intent. The type (class) of the value determines the type of intent.
161      * @param intent intent value
162      * @param distance distance at which the intent applies
163      */
164     public void addIntent(final Object intent, final Length distance)
165     {
166         this.intents.computeIfAbsent(intent.getClass(), (clazz) -> new TreeMap<>()).put(distance, intent);
167     }
168 
169     /**
170      * Returns the nearest value of intent of type given by the class.
171      * @param <T> value type
172      * @param clazz class of type
173      * @return nearest value of intent of type given by the class, empty if no such intent
174      * @throws NullPointerException when any input is {@code null}
175      */
176     @SuppressWarnings("unchecked")
177     public <T> Optional<DistancedObject<T>> getIntent(final Class<T> clazz)
178     {
179         Throw.whenNull(clazz, "clazz");
180         if (this.intents.containsKey(clazz))
181         {
182             Entry<Length, Object> entry = this.intents.get(clazz).firstEntry();
183             return Optional.of(new DistancedObject<>((T) entry.getValue(), entry.getKey()));
184         }
185         return Optional.empty();
186     }
187 
188     /**
189      * Returns the nearest value of intent of type given by the class, if within a distance.
190      * @param <T> value type
191      * @param clazz class of type
192      * @param horizon obtain intent only within this distance
193      * @return nearest value of intent of type given by the class, empty if no such intent
194      * @throws NullPointerException when any input is {@code null}
195      */
196     public <T> Optional<DistancedObject<T>> getIntent(final Class<T> clazz, final Length horizon)
197     {
198         Throw.whenNull(horizon, "horizon");
199         Optional<DistancedObject<T>> intent = getIntent(clazz);
200         if (intent.isPresent() && intent.get().distance().le(horizon))
201         {
202             return intent;
203         }
204         return Optional.empty();
205     }
206 
207     @Override
208     public String getId()
209     {
210         return getUnsafeGtu().getId();
211     }
212 
213     @Override
214     public GtuType getGtuType()
215     {
216         return getUnsafeGtu().getType();
217     }
218 
219     @Override
220     public Parameters getParameters()
221     {
222         if (this.parameters == null)
223         {
224             this.parameters = this.gtu.getParameters();
225         }
226         return this.parameters;
227     }
228 
229     @Override
230     public CarFollowingModel getCarFollowingModel()
231     {
232         if (this.carFollowingModel == null)
233         {
234             this.carFollowingModel = this.gtu.getTacticalPlanner().getCarFollowingModel();
235         }
236         return this.carFollowingModel;
237     }
238 
239     @Override
240     public SpeedLimits getSpeedLimits()
241     {
242         if (this.speedLimits == null)
243         {
244             this.speedLimits = getPerception().getPerceptionCategoryOptional(InfrastructurePerception.class)
245                     .orElseThrow(() -> new NoSuchElementException("No infrastructure perception category."))
246                     .getSpeedLimits(RelativeLane.CURRENT);
247         }
248         return this.speedLimits;
249     }
250 
251     @Override
252     public Speed getDesiredSpeed() throws ParameterException
253     {
254         if (this.desiredSpeed == null)
255         {
256             this.desiredSpeed = getCarFollowingModel().desiredSpeed(getParameters(), getSpeedLimits(), getMaximumSpeed());
257         }
258         return this.desiredSpeed;
259     }
260 
261     @Override
262     public Optional<Route> getRoute()
263     {
264         if (this.route == null)
265         {
266             this.route = getUnsafeGtu().getStrategicalPlanner().getRoute();
267         }
268         return this.route;
269     }
270 
271     @Override
272     public LateralDirectionality getLaneChangeDirection()
273     {
274         return getUnsafeGtu().getLaneChangeDirection();
275     }
276 
277     @Override
278     public Length getLength()
279     {
280         checkEgo();
281         return this.length;
282     }
283 
284     @Override
285     public Length getWidth()
286     {
287         checkEgo();
288         return this.width;
289     }
290 
291     @Override
292     public Speed getSpeed()
293     {
294         checkEgo();
295         return this.speed;
296     }
297 
298     @Override
299     public Speed getMaximumSpeed()
300     {
301         checkEgo();
302         return this.maxSpeed;
303     }
304 
305     @Override
306     public Acceleration getAcceleration()
307     {
308         checkEgo();
309         return this.acceleration;
310     }
311 
312     /**
313      * Check ego information is cached.
314      */
315     private void checkEgo()
316     {
317         if (this.length == null)
318         {
319             EgoPerception<?, ?> ego = getPerception().getPerceptionCategoryOptional(EgoPerception.class)
320                     .orElseThrow(() -> new NoSuchElementException("No ego perception category."));
321             this.length = ego.getLength();
322             this.width = ego.getWidth();
323             this.speed = ego.getSpeed();
324             this.maxSpeed = ego.getMaximumSpeed();
325             this.acceleration = ego.getAcceleration();
326         }
327     }
328 
329     @Override
330     public String toString()
331     {
332         return "TacticalContextEgo [gtu=" + this.gtu.getId() + "]";
333     }
334 
335 }