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.meta.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://tudelft.nl/staff/p.knoppers-1">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      /** End times of active samplings. */
47      private final Map<L, Time> endTimes = new LinkedHashMap<>();
48  
49      /** Space time regions. */
50      private Set<SpaceTimeRegion<L>> spaceTimeRegions = new LinkedHashSet<>();
51  
52      /**
53       * Constructor.
54       * @param extendedDataTypes Set&lt;ExtendedDataType&lt;?, ?, ?, ? super G&gt;&gt;; extended data types.
55       * @param filterDataTypes Set&lt;FilterDataType&lt;?, ? super G&gt;&gt;; filter data types.
56       */
57      public Sampler(final Set<ExtendedDataType<?, ?, ?, ? super G>> extendedDataTypes, final Set<FilterDataType<?, ? super G>> filterDataTypes)
58      {
59          this.extendedDataTypes = new LinkedHashSet<>(extendedDataTypes);
60          this.filterDataTypes = new LinkedHashSet<>(filterDataTypes);
61          this.samplerData = new SamplerData<>(extendedDataTypes, filterDataTypes);
62      }
63  
64      /**
65       * Underlying sampler data.
66       * @return SamplerData&lt;G&gt;; underlying sampler data
67       */
68      public SamplerData<G> getSamplerData()
69      {
70          return this.samplerData;
71      }
72  
73      /**
74       * Whether this sampler has the given extended data type registered to it.
75       * @param extendedDataType ExtendedDataType&lt;?,?,?,?&gt;; extended data type
76       * @return whether this sampler has the given extended data type registered to it
77       */
78      public boolean contains(final ExtendedDataType<?, ?, ?, ?> extendedDataType)
79      {
80          return this.extendedDataTypes.contains(extendedDataType);
81      }
82  
83      /**
84       * Registers a space-time region. Data will be recorded across the entire length of a lane, but only during specified time
85       * periods.
86       * @param spaceTimeRegion SpaceTimeRegion&lt;L&gt;; space-time region
87       * @throws IllegalStateException if data is not available from the requested start time
88       */
89      public final void registerSpaceTimeRegion(final SpaceTimeRegion<L> spaceTimeRegion)
90      {
91          Throw.whenNull(spaceTimeRegion, "SpaceTimeRegion may not be null.");
92          Time firstPossibleDataTime;
93          if (this.samplerData.contains(spaceTimeRegion.lane()))
94          {
95              firstPossibleDataTime = this.samplerData.getTrajectoryGroup(spaceTimeRegion.lane()).getStartTime();
96          }
97          else
98          {
99              firstPossibleDataTime = now();
100         }
101         Throw.when(spaceTimeRegion.startTime().lt(firstPossibleDataTime), IllegalStateException.class,
102                 "Space time region with start time %s is defined while data is available from %s onwards.",
103                 spaceTimeRegion.startTime(), firstPossibleDataTime);
104         if (this.samplerData.contains(spaceTimeRegion.lane()))
105         {
106             this.endTimes.put(spaceTimeRegion.lane(),
107                     Time.max(this.endTimes.get(spaceTimeRegion.lane()), spaceTimeRegion.endTime()));
108         }
109         else
110         {
111             this.endTimes.put(spaceTimeRegion.lane(), spaceTimeRegion.endTime());
112             scheduleStartRecording(spaceTimeRegion.startTime(), spaceTimeRegion.lane());
113         }
114         scheduleStopRecording(this.endTimes.get(spaceTimeRegion.lane()), spaceTimeRegion.lane());
115         this.spaceTimeRegions.add(spaceTimeRegion);
116     }
117 
118     /**
119      * Returns the current simulation time.
120      * @return current simulation time
121      */
122     public abstract Time now();
123 
124     /**
125      * Schedules the start of recording for a given lane, i.e. the implementation has to invoke {@code startRecording} at the
126      * specified time, with the given lane as input. In case multiple space time-regions are registered for the same lane, this
127      * method is invoked whenever the next space-time region that is added has an earlier start time than any before.
128      * @param time Time; time to start recording
129      * @param lane L; lane to start recording
130      */
131     public abstract void scheduleStartRecording(Time time, L lane);
132 
133     /**
134      * Schedules the stop of recording for a given lane, i.e. the implementation has to invoke {@code stopRecording} at the
135      * specified time, with the given lane as input. In case multiple space time-regions are registered for the same lane, this
136      * method is invoked whenever the next space-time region that is added has a late end time than any before.
137      * @param time Time; time to stop recording
138      * @param lane L; lane to stop recording
139      */
140     public abstract void scheduleStopRecording(Time time, L lane);
141 
142     /**
143      * Start recording at the given time (which should be the current time) on the given lane.
144      * @param lane L; lane
145      */
146     public final void startRecording(final L lane)
147     {
148         Throw.whenNull(lane, "LaneData may not be null.");
149         if (this.samplerData.contains(lane))
150         {
151             return;
152         }
153         this.samplerData.putTrajectoryGroup(lane, new TrajectoryGroup<>(now(), lane));
154         initRecording(lane);
155     }
156 
157     /**
158      * Adds listeners to start recording.
159      * @param lane L; lane to initialize recording for
160      */
161     public abstract void initRecording(L lane);
162 
163     /**
164      * Stop recording at given lane.
165      * @param lane L; lane
166      */
167     public final void stopRecording(final L lane)
168     {
169         Throw.whenNull(lane, "LaneData may not be null.");
170         if (!this.samplerData.contains(lane) || this.endTimes.get(lane).gt(now()))
171         {
172             return;
173         }
174         finalizeRecording(lane);
175     }
176 
177     /**
178      * Remove listeners to stop recording.
179      * @param lane L; lane
180      */
181     public abstract void finalizeRecording(L lane);
182 
183     /**
184      * Creates a trajectory with the current snapshot of a GTU.
185      * @param lane L; lane the gtu is at
186      * @param position Length; position of the gtu on the lane
187      * @param speed Speed; speed of the gtu
188      * @param acceleration Acceleration; acceleration of the gtu
189      * @param time Time; current time
190      * @param gtu G; gtu
191      */
192     public final void processGtuAddEventWithMove(final L lane, final Length position, final Speed speed,
193             final Acceleration acceleration, final Time time, final G gtu)
194     {
195         Throw.whenNull(lane, "LaneData may not be null.");
196         Throw.whenNull(position, "Position may not be null.");
197         if (lane.getLength().lt(position))
198         {
199             // ignore event if beyond lane length (may happen during lane change)
200             return;
201         }
202         processGtuAddEvent(lane, gtu);
203         processGtuMoveEvent(lane, position, speed, acceleration, time, gtu);
204     }
205 
206     /**
207      * Creates a trajectory, including filter data.
208      * @param lane L; lane the gtu is at
209      * @param gtu G; gtu
210      */
211     public final void processGtuAddEvent(final L lane, final G gtu)
212     {
213         Throw.whenNull(lane, "LaneData may not be null.");
214         Throw.whenNull(gtu, "GtuData may not be null.");
215         String gtuId = gtu.getId();
216         Trajectory<G> trajectory = new Trajectory<G>(gtu, makeFilterData(gtu), this.extendedDataTypes, lane);
217         this.trajectoryPerGtu.computeIfAbsent(gtuId, (key) -> new LinkedHashMap<>()).put(lane, trajectory);
218         this.samplerData.getTrajectoryGroup(lane).addTrajectory(trajectory);
219     }
220 
221     /**
222      * Adds a new snapshot of a GTU to its recording trajectory, if recorded. This method may be invoked on GTU that are not
223      * being recorded; the event will then be ignored.
224      * @param lane L; lane the gtu is at
225      * @param position Length; position of the gtu on the lane
226      * @param speed Speed; speed of the gtu
227      * @param acceleration Acceleration; acceleration of the gtu
228      * @param time Time; current time
229      * @param gtu G; gtu
230      */
231     public final void processGtuMoveEvent(final L lane, final Length position, final Speed speed,
232             final Acceleration acceleration, final Time time, final G gtu)
233     {
234         Throw.whenNull(lane, "LaneData may not be null.");
235         Throw.whenNull(position, "Position may not be null.");
236         Throw.whenNull(speed, "Speed may not be null.");
237         Throw.whenNull(acceleration, "Acceleration may not be null.");
238         Throw.whenNull(time, "Time may not be null.");
239         Throw.whenNull(gtu, "GtuData may not be null.");
240         String gtuId = gtu.getId();
241         Map<L, Trajectory<G>> trajectoryPerLane = this.trajectoryPerGtu.get(gtuId);
242         if (trajectoryPerLane != null)
243         {
244             Trajectory<G> trajectory = trajectoryPerLane.get(lane);
245             if (trajectory != null)
246             {
247                 trajectory.add(position, speed, acceleration, time, gtu);
248             }
249         }
250     }
251 
252     /**
253      * Finalizes a trajectory with the current snapshot of a GTU.
254      * @param lane L; lane the gtu is at
255      * @param position Length; position of the gtu on the lane
256      * @param speed Speed; speed of the gtu
257      * @param acceleration Acceleration; acceleration of the gtu
258      * @param time Time; current time
259      * @param gtu G; gtu
260      */
261     public final void processGtuRemoveEventWithMove(final L lane, final Length position, final Speed speed,
262             final Acceleration acceleration, final Time time, final G gtu)
263     {
264         processGtuMoveEvent(lane, position, speed, acceleration, time, gtu);
265         processGtuRemoveEvent(lane, gtu);
266     }
267 
268     /**
269      * Finalizes a trajectory.
270      * @param lane L; lane the gtu is at
271      * @param gtu G; gtu
272      */
273     public final void processGtuRemoveEvent(final L lane, final G gtu)
274     {
275         Throw.whenNull(lane, "LaneData may not be null.");
276         Throw.whenNull(gtu, "GtuData may not be null.");
277         String gtuId = gtu.getId();
278         Map<L, Trajectory<G>> trajectoryPerLane = this.trajectoryPerGtu.get(gtuId);
279         if (trajectoryPerLane != null)
280         {
281             trajectoryPerLane.remove(lane);
282             if (trajectoryPerLane.isEmpty())
283             {
284                 this.trajectoryPerGtu.remove(gtuId);
285             }
286         }
287     }
288 
289     /**
290      * Gathers the filter data for filter data types.
291      * @param gtu G; gtu to return filter data for a GTU
292      * @return filter data for the given gtu
293      */
294     private Map<FilterDataType<?, ? super G>, Object> makeFilterData(final G gtu)
295     {
296         Map<FilterDataType<?, ? super G>, Object> filterData = new LinkedHashMap<>();
297         this.filterDataTypes.forEach((filterDataType) -> filterData.put(filterDataType, filterDataType.getValue(gtu)));
298         return filterData;
299     }
300 
301     /** {@inheritDoc} */
302     @Override
303     public int hashCode()
304     {
305         return Objects.hash(this.extendedDataTypes, this.filterDataTypes, this.spaceTimeRegions);
306     }
307 
308     /** {@inheritDoc} */
309     @Override
310     public boolean equals(final Object obj)
311     {
312         if (this == obj)
313         {
314             return true;
315         }
316         if (obj == null)
317         {
318             return false;
319         }
320         if (getClass() != obj.getClass())
321         {
322             return false;
323         }
324         Sampler<?, ?> other = (Sampler<?, ?>) obj;
325         return Objects.equals(this.extendedDataTypes, other.extendedDataTypes)
326                 && Objects.equals(this.filterDataTypes, other.filterDataTypes)
327                 && Objects.equals(this.spaceTimeRegions, other.spaceTimeRegions);
328     }
329 
330 }