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.Set;
7   
8   import org.djunits.value.vdouble.scalar.Acceleration;
9   import org.djunits.value.vdouble.scalar.Length;
10  import org.djunits.value.vdouble.scalar.Speed;
11  import org.djunits.value.vdouble.scalar.Time;
12  import org.djutils.exceptions.Throw;
13  import org.opentrafficsim.kpi.interfaces.GtuDataInterface;
14  import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
15  import org.opentrafficsim.kpi.sampling.meta.FilterDataType;
16  import org.opentrafficsim.kpi.sampling.meta.MetaData;
17  
18  /**
19   * Sampler is the highest level organizer for sampling.
20   * <p>
21   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
22   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
23   * <p>
24   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Sep 22, 2016 <br>
25   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
26   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
27   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
28   * @param <G> gtu data type
29   */
30  public abstract class Sampler<G extends GtuDataInterface>
31  {
32  
33      /** Sampler data. */
34      private final SamplerData<G> samplerData;
35  
36      /** Registration of included extended data types. */
37      private final Set<ExtendedDataType<?, ?, ?, G>> extendedDataTypes;
38  
39      /** Set of registered filter data types. */
40      private final Set<FilterDataType<?>> filterDataTypes;
41  
42      /** Registration of current trajectories of each GTU per lane. */
43      private final Map<String, Map<KpiLaneDirection, Trajectory<G>>> trajectoryPerGtu = new LinkedHashMap<>();
44  
45      /** End times of active samplings. */
46      private final Map<KpiLaneDirection, Time> endTimes = new LinkedHashMap<>();
47  
48      /** Space time regions. */
49      private Set<SpaceTimeRegion> spaceTimeRegions = new LinkedHashSet<>();
50  
51      /**
52       * Constructor.
53       * @param extendedDataTypes Set&lt;ExtendedDataType&lt;?, ?, ?, G&gt;&gt;; extended data types
54       * @param filterDataTypes Set&lt;FilterDataType&lt;?&gt;&gt;; filter data types
55       */
56      @SuppressWarnings({"unchecked", "rawtypes"})
57      public Sampler(final Set<ExtendedDataType<?, ?, ?, G>> extendedDataTypes, final Set<FilterDataType<?>> filterDataTypes)
58      {
59          this.extendedDataTypes = new LinkedHashSet<>(extendedDataTypes);
60          this.filterDataTypes = new LinkedHashSet<>(filterDataTypes);
61          Set<Column<?>> columns = new LinkedHashSet<>();
62          // TODO: fixed columns!
63          for (ExtendedDataType<?, ?, ?, G> extendedDataType : this.extendedDataTypes)
64          {
65              columns.add(new SimpleColumn(extendedDataType.getId(), extendedDataType.getId(), extendedDataType.getType()));
66          }
67          for (FilterDataType<?> filterDataType : this.filterDataTypes)
68          {
69              columns.add(new SimpleColumn(filterDataType.getId(), filterDataType.getId(), String.class));
70          }
71          this.samplerData = new SamplerData<>(columns);
72      }
73  
74      /**
75       * Underlying sampler data.
76       * @return SamplerData&lt;G&gt;; underlying sampler data
77       */
78      public SamplerData<G> getSamplerData()
79      {
80          return this.samplerData;
81      }
82  
83      /**
84       * Whether this sampler has the given extended data type registered to it.
85       * @param extendedDataType ExtendedDataType&lt;?,?,?,?&gt;; extended data type
86       * @return whether this sampler has the given extended data type registered to it
87       */
88      public boolean contains(final ExtendedDataType<?, ?, ?, ?> extendedDataType)
89      {
90          return this.extendedDataTypes.contains(extendedDataType);
91      }
92  
93      /**
94       * Registers a space-time region. Data will be recorded across the entire length of a lane, but only during specified time
95       * periods.
96       * @param spaceTimeRegion SpaceTimeRegion; space-time region
97       * @throws IllegalStateException if data is not available from the requested start time
98       */
99      public final void registerSpaceTimeRegion(final SpaceTimeRegion spaceTimeRegion)
100     {
101         Throw.whenNull(spaceTimeRegion, "SpaceTimeRegion may not be null.");
102         Time firstPossibleDataTime;
103         if (this.samplerData.contains(spaceTimeRegion.getLaneDirection()))
104         {
105             firstPossibleDataTime = this.samplerData.getTrajectoryGroup(spaceTimeRegion.getLaneDirection()).getStartTime();
106         }
107         else
108         {
109             firstPossibleDataTime = now();
110         }
111         Throw.when(spaceTimeRegion.getStartTime().lt(firstPossibleDataTime), IllegalStateException.class,
112             "Space time region with start time %s is defined while data is available from %s onwards.", spaceTimeRegion
113                 .getStartTime(), firstPossibleDataTime);
114         if (this.samplerData.contains(spaceTimeRegion.getLaneDirection()))
115         {
116             this.endTimes.put(spaceTimeRegion.getLaneDirection(), Time.max(this.endTimes.get(spaceTimeRegion
117                 .getLaneDirection()), spaceTimeRegion.getEndTime()));
118         }
119         else
120         {
121             this.endTimes.put(spaceTimeRegion.getLaneDirection(), spaceTimeRegion.getEndTime());
122             scheduleStartRecording(spaceTimeRegion.getStartTime(), spaceTimeRegion.getLaneDirection());
123         }
124         scheduleStopRecording(this.endTimes.get(spaceTimeRegion.getLaneDirection()), spaceTimeRegion.getLaneDirection());
125         this.spaceTimeRegions.add(spaceTimeRegion);
126     }
127 
128     /**
129      * Returns the current simulation time.
130      * @return current simulation time
131      */
132     public abstract Time now();
133 
134     /**
135      * Schedules the start of recording for a given lane-direction.
136      * @param time Time; time to start recording
137      * @param kpiLaneDirection KpiLaneDirection; lane-direction to start recording
138      */
139     public abstract void scheduleStartRecording(Time time, KpiLaneDirection kpiLaneDirection);
140 
141     /**
142      * Schedules the stop of recording for a given lane-direction.
143      * @param time Time; time to stop recording
144      * @param kpiLaneDirection KpiLaneDirection; lane-direction to stop recording
145      */
146     public abstract void scheduleStopRecording(Time time, KpiLaneDirection kpiLaneDirection);
147 
148     /**
149      * Start recording at the given time (which should be the current time) on the given lane direction.
150      * @param kpiLaneDirection KpiLaneDirection; lane direction
151      */
152     public final void startRecording(final KpiLaneDirection kpiLaneDirection)
153     {
154         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
155         if (this.samplerData.contains(kpiLaneDirection))
156         {
157             return;
158         }
159         this.samplerData.putTrajectoryGroup(kpiLaneDirection, new TrajectoryGroup<>(now(), kpiLaneDirection));
160         initRecording(kpiLaneDirection);
161     }
162 
163     /**
164      * Adds listeners to start recording.
165      * @param kpiLaneDirection KpiLaneDirection; lane direction to initialize recording for
166      */
167     public abstract void initRecording(KpiLaneDirection kpiLaneDirection);
168 
169     /**
170      * Stop recording at given lane direction.
171      * @param kpiLaneDirection KpiLaneDirection; lane direction
172      */
173     public final void stopRecording(final KpiLaneDirection kpiLaneDirection)
174     {
175         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
176         if (!this.samplerData.contains(kpiLaneDirection) || this.endTimes.get(kpiLaneDirection).gt(now()))
177         {
178             return;
179         }
180         finalizeRecording(kpiLaneDirection);
181     }
182 
183     /**
184      * Remove listeners to stop recording.
185      * @param kpiLaneDirection KpiLaneDirection; lane direction to finalize recording for
186      */
187     public abstract void finalizeRecording(KpiLaneDirection kpiLaneDirection);
188 
189     /**
190      * Creates a trajectory with the current snapshot of a GTU.
191      * @param kpiLaneDirection KpiLaneDirection; lane direction the gtu is at
192      * @param position Length; position of the gtu on the lane
193      * @param speed Speed; speed of the gtu
194      * @param acceleration Acceleration; acceleration of the gtu
195      * @param time Time; current time
196      * @param gtu G; gtu
197      */
198     public final void processGtuAddEvent(final KpiLaneDirection kpiLaneDirection, final Length position, final Speed speed,
199             final Acceleration acceleration, final Time time, final G gtu)
200     {
201         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
202         Throw.whenNull(position, "Position may not be null.");
203         Throw.whenNull(speed, "Speed may not be null.");
204         Throw.whenNull(acceleration, "Acceleration may not be null.");
205         Throw.whenNull(time, "Time may not be null.");
206         Throw.whenNull(gtu, "GtuDataInterface may not be null.");
207         if (kpiLaneDirection.getLaneData().getLength().lt(position))
208         {
209             // ignore event if beyond lane length (may happen during lane change)
210             return;
211         }
212         String gtuId = gtu.getId();
213         Trajectory<G> trajectory = new Trajectory<>(gtu, makeMetaData(gtu), this.extendedDataTypes, kpiLaneDirection);
214         if (!this.trajectoryPerGtu.containsKey(gtuId))
215         {
216             Map<KpiLaneDirection, Trajectory<G>> map = new LinkedHashMap<>();
217             this.trajectoryPerGtu.put(gtuId, map);
218         }
219         this.trajectoryPerGtu.get(gtuId).put(kpiLaneDirection, trajectory);
220         this.samplerData.getTrajectoryGroup(kpiLaneDirection).addTrajectory(trajectory);
221         processGtuMoveEvent(kpiLaneDirection, position, speed, acceleration, time, gtu);
222     }
223 
224     /**
225      * Adds a new snapshot of a GTU to its recording trajectory, if recorded. This method may be invoked on GTU that are not
226      * being recorded; the event will then be ignored.
227      * @param kpiLaneDirection KpiLaneDirection; lane direction the gtu is at
228      * @param position Length; position of the gtu on the lane
229      * @param speed Speed; speed of the gtu
230      * @param acceleration Acceleration; acceleration of the gtu
231      * @param time Time; current time
232      * @param gtu G; gtu
233      */
234     public final void processGtuMoveEvent(final KpiLaneDirection kpiLaneDirection, final Length position, final Speed speed,
235             final Acceleration acceleration, final Time time, final G gtu)
236     {
237         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
238         Throw.whenNull(position, "Position may not be null.");
239         Throw.whenNull(speed, "Speed may not be null.");
240         Throw.whenNull(acceleration, "Acceleration may not be null.");
241         Throw.whenNull(time, "Time may not be null.");
242         Throw.whenNull(gtu, "GtuDataInterface may not be null.");
243         String gtuId = gtu.getId();
244         if (this.trajectoryPerGtu.containsKey(gtuId) && this.trajectoryPerGtu.get(gtuId).containsKey(kpiLaneDirection))
245         {
246             this.trajectoryPerGtu.get(gtuId).get(kpiLaneDirection).add(position, speed, acceleration, time, gtu);
247         }
248     }
249 
250     /**
251      * Finalizes a trajectory with the current snapshot of a GTU.
252      * @param kpiLaneDirection KpiLaneDirection; lane direction the gtu is at
253      * @param position Length; position of the gtu on the lane
254      * @param speed Speed; speed of the gtu
255      * @param acceleration Acceleration; acceleration of the gtu
256      * @param time Time; current time
257      * @param gtu G; gtu
258      */
259     public final void processGtuRemoveEvent(final KpiLaneDirection kpiLaneDirection, final Length position, final Speed speed,
260             final Acceleration acceleration, final Time time, final G gtu)
261     {
262         processGtuMoveEvent(kpiLaneDirection, position, speed, acceleration, time, gtu);
263         processGtuRemoveEvent(kpiLaneDirection, gtu);
264     }
265 
266     /**
267      * Finalizes a trajectory.
268      * @param kpiLaneDirection KpiLaneDirection; lane direction the gtu is at
269      * @param gtu G; gtu
270      */
271     public final void processGtuRemoveEvent(final KpiLaneDirection kpiLaneDirection, final G gtu)
272     {
273         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
274         Throw.whenNull(gtu, "GtuDataInterface may not be null.");
275         String gtuId = gtu.getId();
276         if (this.trajectoryPerGtu.containsKey(gtuId))
277         {
278             this.trajectoryPerGtu.get(gtuId).remove(kpiLaneDirection);
279             if (this.trajectoryPerGtu.get(gtuId).isEmpty())
280             {
281                 this.trajectoryPerGtu.remove(gtuId);
282             }
283         }
284     }
285 
286     /**
287      * @param gtu G; gtu to return meta data for
288      * @param <T> underlying type of a meta data type
289      * @return meta data for the given gtu
290      */
291     @SuppressWarnings("unchecked")
292     private <T> MetaData makeMetaData(final G gtu)
293     {
294         MetaDatameta/MetaData.html#MetaData">MetaData metaData = new MetaData();
295         for (FilterDataType<?> metaDataType : this.filterDataTypes)
296         {
297             T value = (T) metaDataType.getValue(gtu);
298             if (value != null)
299             {
300                 metaData.put((FilterDataType<T>) metaDataType, value);
301             }
302         }
303         return metaData;
304     }
305 
306     // TODO: hashCode / equals
307 
308 }