View Javadoc
1   package org.opentrafficsim.kpi.sampling;
2   
3   import java.util.LinkedHashMap;
4   import java.util.LinkedHashSet;
5   import java.util.Map;
6   import java.util.Objects;
7   import java.util.Set;
8   
9   import org.djunits.value.vdouble.scalar.Acceleration;
10  import org.djunits.value.vdouble.scalar.Length;
11  import org.djunits.value.vdouble.scalar.Speed;
12  import org.djunits.value.vdouble.scalar.Time;
13  import org.djutils.exceptions.Throw;
14  import org.opentrafficsim.kpi.interfaces.GtuData;
15  import org.opentrafficsim.kpi.interfaces.LaneData;
16  import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
17  import org.opentrafficsim.kpi.sampling.filter.FilterDataType;
18  
19  /**
20   * Sampler is the highest level organizer for sampling.
21   * <p>
22   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
23   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
24   * </p>
25   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
26   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
27   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
28   * @param <G> GTU data type
29   * @param <L> lane data type
30   */
31  public abstract class Sampler<G extends GtuData, L extends LaneData<L>>
32  {
33  
34      /** Sampler data. */
35      private final SamplerData<G> samplerData;
36  
37      /** Registration of included extended data types. */
38      private final Set<ExtendedDataType<?, ?, ?, ? super G>> extendedDataTypes;
39  
40      /** Set of registered filter data types. */
41      private final Set<FilterDataType<?, ? super G>> filterDataTypes;
42  
43      /** Registration of current trajectories of each GTU per lane. */
44      private final Map<String, Map<L, Trajectory<G>>> trajectoryPerGtu = new LinkedHashMap<>();
45  
46      /** Number of space time regions that currently need data (i.e. overlap counter). */
47      private final Map<L, Integer> currentlyRecording = new LinkedHashMap<>();
48  
49      /**
50       * Constructor.
51       * @param extendedDataTypes extended data types.
52       * @param filterDataTypes filter data types.
53       */
54      public Sampler(final Set<ExtendedDataType<?, ?, ?, ? super G>> extendedDataTypes,
55              final Set<FilterDataType<?, ? super G>> filterDataTypes)
56      {
57          this.extendedDataTypes = new LinkedHashSet<>(extendedDataTypes);
58          this.filterDataTypes = new LinkedHashSet<>(filterDataTypes);
59          this.samplerData = new SamplerData<>(extendedDataTypes, filterDataTypes);
60      }
61  
62      /**
63       * Underlying sampler data.
64       * @return underlying sampler data
65       */
66      public SamplerData<G> getSamplerData()
67      {
68          return this.samplerData;
69      }
70  
71      /**
72       * Whether this sampler has the given extended data type registered to it.
73       * @param extendedDataType extended data type
74       * @return whether this sampler has the given extended data type registered to it
75       */
76      public boolean contains(final ExtendedDataType<?, ?, ?, ?> extendedDataType)
77      {
78          return this.extendedDataTypes.contains(extendedDataType);
79      }
80  
81      /**
82       * Registers a space-time region. Data will be recorded across the entire length of a lane, but only during specified time
83       * periods.
84       * @param spaceTimeRegion space-time region
85       * @throws IllegalStateException if data is not available from the requested start time
86       */
87      public final void registerSpaceTimeRegion(final SpaceTimeRegion<L> spaceTimeRegion)
88      {
89          Throw.whenNull(spaceTimeRegion, "SpaceTimeRegion may not be null.");
90          Time firstPossibleDataTime;
91          if (this.samplerData.contains(spaceTimeRegion.lane()))
92          {
93              firstPossibleDataTime = this.samplerData.getTrajectoryGroup(spaceTimeRegion.lane()).getStartTime();
94          }
95          else
96          {
97              firstPossibleDataTime = now();
98          }
99          Throw.when(spaceTimeRegion.startTime().lt(firstPossibleDataTime), IllegalStateException.class,
100                 "Space time region with start time %s is defined while data is available from %s onwards.",
101                 spaceTimeRegion.startTime(), firstPossibleDataTime);
102         scheduleStartRecording(spaceTimeRegion.startTime(), spaceTimeRegion.lane());
103         scheduleStopRecording(spaceTimeRegion.endTime(), spaceTimeRegion.lane());
104     }
105 
106     /**
107      * Returns the current simulation time.
108      * @return current simulation time
109      */
110     public abstract Time now();
111 
112     /**
113      * Schedules the start of recording for a given lane, i.e. the implementation has to invoke {@code startRecording} at the
114      * specified time, with the given lane as input. In case multiple space time-regions are registered for the same lane, this
115      * method is invoked whenever the next space-time region that is added has an earlier start time than any before.
116      * @param time time to start recording
117      * @param lane lane to start recording
118      */
119     public abstract void scheduleStartRecording(Time time, L lane);
120 
121     /**
122      * Schedules the stop of recording for a given lane, i.e. the implementation has to invoke {@code stopRecording} at the
123      * specified time, with the given lane as input. In case multiple space time-regions are registered for the same lane, this
124      * method is invoked whenever the next space-time region that is added has a later end time than any before.
125      * @param time time to stop recording
126      * @param lane lane to stop recording
127      */
128     public abstract void scheduleStopRecording(Time time, L lane);
129 
130     /**
131      * Start recording at the given time (which should be the current time) on the given lane.
132      * @param lane lane
133      */
134     public final void startRecording(final L lane)
135     {
136         Throw.whenNull(lane, "LaneData may not be null.");
137         if (this.currentlyRecording.containsKey(lane))
138         {
139             this.currentlyRecording.compute(lane, (l, i) -> i + 1);
140             return;
141         }
142         this.currentlyRecording.put(lane, 0);
143         if (!this.samplerData.contains(lane))
144         {
145             this.samplerData.putTrajectoryGroup(lane, new TrajectoryGroup<>(now(), lane));
146         }
147         initRecording(lane);
148     }
149 
150     /**
151      * Adds listeners to start recording.
152      * @param lane lane to initialize recording for
153      */
154     public abstract void initRecording(L lane);
155 
156     /**
157      * Stop recording at given lane.
158      * @param lane lane
159      */
160     public final void stopRecording(final L lane)
161     {
162         Throw.whenNull(lane, "LaneData may not be null.");
163         if (!this.currentlyRecording.containsKey(lane))
164         {
165             return; // wrong invocation; ignore
166         }
167         if (this.currentlyRecording.get(lane) > 0)
168         {
169             this.currentlyRecording.compute(lane, (l, i) -> i - 1);
170             return;
171         }
172         this.currentlyRecording.remove(lane);
173         finalizeRecording(lane);
174     }
175 
176     /**
177      * Remove listeners to stop recording.
178      * @param lane lane
179      */
180     public abstract void finalizeRecording(L lane);
181 
182     /**
183      * Creates a trajectory with the current snapshot of a GTU.
184      * @param lane lane the gtu is at
185      * @param position position of the gtu on the lane
186      * @param speed speed of the gtu
187      * @param acceleration acceleration of the gtu
188      * @param time current time
189      * @param gtu gtu
190      */
191     public final void addGtuWithSnapshot(final L lane, final Length position, final Speed speed,
192             final Acceleration acceleration, final Time time, final G gtu)
193     {
194         Throw.whenNull(lane, "LaneData may not be null.");
195         Throw.whenNull(position, "Position may not be null.");
196         if (lane.getLength().lt(position))
197         {
198             // ignore event if beyond lane length (may happen during lane change)
199             return;
200         }
201         addGtu(lane, gtu);
202         snapshot(lane, position, speed, acceleration, time, gtu);
203     }
204 
205     /**
206      * Creates a trajectory, including filter data.
207      * @param lane lane the gtu is at
208      * @param gtu gtu
209      */
210     public final void addGtu(final L lane, final G gtu)
211     {
212         Throw.whenNull(lane, "LaneData may not be null.");
213         Throw.whenNull(gtu, "GtuData may not be null.");
214         String gtuId = gtu.getId();
215         Trajectory<G> trajectory = new Trajectory<G>(gtu, makeFilterData(gtu), this.extendedDataTypes);
216         this.trajectoryPerGtu.computeIfAbsent(gtuId, (key) -> new LinkedHashMap<>()).put(lane, trajectory);
217         this.samplerData.getTrajectoryGroup(lane).addTrajectory(trajectory);
218     }
219 
220     /**
221      * Adds a new snapshot of a GTU to its recording trajectory, if recorded. This method may be invoked on GTUs that are not
222      * being recorded; the event will then be ignored.
223      * @param lane lane the gtu is at
224      * @param position position of the gtu on the lane
225      * @param speed speed of the gtu
226      * @param acceleration acceleration of the gtu
227      * @param time current time
228      * @param gtu gtu
229      */
230     public final void snapshot(final L lane, final Length position, final Speed speed, final Acceleration acceleration,
231             final Time time, final G gtu)
232     {
233         Throw.whenNull(lane, "LaneData may not be null.");
234         Throw.whenNull(position, "Position may not be null.");
235         Throw.whenNull(speed, "Speed may not be null.");
236         Throw.whenNull(acceleration, "Acceleration may not be null.");
237         Throw.whenNull(time, "Time may not be null.");
238         Throw.whenNull(gtu, "GtuData may not be null.");
239         String gtuId = gtu.getId();
240         Map<L, Trajectory<G>> trajectoryPerLane = this.trajectoryPerGtu.get(gtuId);
241         if (trajectoryPerLane != null)
242         {
243             Trajectory<G> trajectory = trajectoryPerLane.get(lane);
244             if (trajectory != null)
245             {
246                 trajectory.add(position, speed, acceleration, time, gtu);
247             }
248         }
249     }
250 
251     /**
252      * Finalizes a trajectory with the current snapshot of a GTU.
253      * @param lane lane the gtu is at
254      * @param position position of the gtu on the lane
255      * @param speed speed of the gtu
256      * @param acceleration acceleration of the gtu
257      * @param time current time
258      * @param gtu gtu
259      */
260     public final void removeGtuWithSnapshot(final L lane, final Length position, final Speed speed,
261             final Acceleration acceleration, final Time time, final G gtu)
262     {
263         snapshot(lane, position, speed, acceleration, time, gtu);
264         removeGtu(lane, gtu);
265     }
266 
267     /**
268      * Finalizes a trajectory.
269      * @param lane lane the gtu is at
270      * @param gtu gtu
271      */
272     public final void removeGtu(final L lane, final G gtu)
273     {
274         Throw.whenNull(lane, "LaneData may not be null.");
275         Throw.whenNull(gtu, "GtuData may not be null.");
276         String gtuId = gtu.getId();
277         Map<L, Trajectory<G>> trajectoryPerLane = this.trajectoryPerGtu.get(gtuId);
278         if (trajectoryPerLane != null)
279         {
280             trajectoryPerLane.remove(lane);
281             if (trajectoryPerLane.isEmpty())
282             {
283                 this.trajectoryPerGtu.remove(gtuId);
284             }
285         }
286     }
287 
288     /**
289      * Gathers the filter data for filter data types.
290      * @param gtu gtu to return filter data for a GTU
291      * @return filter data for the given gtu
292      */
293     private Map<FilterDataType<?, ? super G>, Object> makeFilterData(final G gtu)
294     {
295         Map<FilterDataType<?, ? super G>, Object> filterData = new LinkedHashMap<>();
296         this.filterDataTypes.forEach((filterDataType) -> filterData.put(filterDataType, filterDataType.getValue(gtu)));
297         return filterData;
298     }
299 
300     @Override
301     public int hashCode()
302     {
303         return Objects.hash(this.extendedDataTypes, this.filterDataTypes);
304     }
305 
306     @Override
307     public boolean equals(final Object obj)
308     {
309         if (this == obj)
310         {
311             return true;
312         }
313         if (obj == null)
314         {
315             return false;
316         }
317         if (getClass() != obj.getClass())
318         {
319             return false;
320         }
321         Sampler<?, ?> other = (Sampler<?, ?>) obj;
322         return Objects.equals(this.extendedDataTypes, other.extendedDataTypes)
323                 && Objects.equals(this.filterDataTypes, other.filterDataTypes);
324     }
325 
326 }