View Javadoc
1   package org.opentrafficsim.kpi.sampling;
2   
3   import java.io.BufferedWriter;
4   import java.io.File;
5   import java.io.FileOutputStream;
6   import java.io.IOException;
7   import java.io.OutputStreamWriter;
8   import java.util.ArrayList;
9   import java.util.HashMap;
10  import java.util.LinkedHashMap;
11  import java.util.LinkedHashSet;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.Set;
15  import java.util.zip.ZipEntry;
16  import java.util.zip.ZipOutputStream;
17  
18  import org.djunits.value.vdouble.scalar.Acceleration;
19  import org.djunits.value.vdouble.scalar.Length;
20  import org.djunits.value.vdouble.scalar.Speed;
21  import org.djunits.value.vdouble.scalar.Time;
22  import org.opentrafficsim.kpi.interfaces.GtuDataInterface;
23  import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
24  import org.opentrafficsim.kpi.sampling.meta.MetaData;
25  import org.opentrafficsim.kpi.sampling.meta.MetaDataType;
26  
27  import nl.tudelft.simulation.language.Throw;
28  
29  /**
30   * Sampler is the highest level organizer for sampling.
31   * <p>
32   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
33   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
34   * <p>
35   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Sep 22, 2016 <br>
36   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
37   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
38   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
39   */
40  public abstract class Sampler
41  {
42  
43      /** Map with all sampling data. */
44      private final Map<KpiLaneDirection, TrajectoryGroup> trajectories = new LinkedHashMap<>();
45  
46      /** End times of active samplings. */
47      private final Map<KpiLaneDirection, Time> endTimes = new LinkedHashMap<>();
48  
49      /** Registration of current trajectories of each GTU per lane. */
50      private final Map<String, Map<KpiLaneDirection, Trajectory>> trajectoryPerGtu = new LinkedHashMap<>();
51  
52      /** Registration of included extended data types. */
53      private final Set<ExtendedDataType<?, ?, ?>> extendedDataTypes = new LinkedHashSet<>();
54  
55      /** Set of registered meta data types. */
56      private Set<MetaDataType<?>> registeredMetaDataTypes = new LinkedHashSet<>();
57  
58      /**
59       * @param spaceTimeRegion space-time region
60       * @throws IllegalStateException if data is not available from the requested start time
61       */
62      public final void registerSpaceTimeRegion(final SpaceTimeRegion spaceTimeRegion)
63      {
64          Throw.whenNull(spaceTimeRegion, "SpaceTimeRegion may not be null.");
65          Time firstPossibleDataTime;
66          if (this.trajectories.containsKey(spaceTimeRegion.getLaneDirection()))
67          {
68              firstPossibleDataTime = this.trajectories.get(spaceTimeRegion.getLaneDirection()).getStartTime();
69          }
70          else
71          {
72              firstPossibleDataTime = now();
73          }
74          Throw.when(spaceTimeRegion.getStartTime().lt(firstPossibleDataTime), IllegalStateException.class,
75                  "Space time region with start time %s is defined while data is available from %s onwards.",
76                  spaceTimeRegion.getStartTime(), firstPossibleDataTime);
77          if (this.trajectories.containsKey(spaceTimeRegion.getLaneDirection()))
78          {
79              this.endTimes.put(spaceTimeRegion.getLaneDirection(),
80                      Time.max(this.endTimes.get(spaceTimeRegion.getLaneDirection()), spaceTimeRegion.getEndTime()));
81          }
82          else
83          {
84              this.endTimes.put(spaceTimeRegion.getLaneDirection(), spaceTimeRegion.getEndTime());
85              scheduleStartRecording(spaceTimeRegion.getStartTime(), spaceTimeRegion.getLaneDirection());
86          }
87          scheduleStopRecording(this.endTimes.get(spaceTimeRegion.getLaneDirection()), spaceTimeRegion.getLaneDirection());
88      }
89  
90      /**
91       * Returns the current simulation time.
92       * @return current simulation time
93       */
94      public abstract Time now();
95  
96      /**
97       * Schedules the start of recording for a given lane-direction.
98       * @param time time to start recording
99       * @param kpiLaneDirection lane-direction to start recording
100      */
101     public abstract void scheduleStartRecording(final Time time, final KpiLaneDirection kpiLaneDirection);
102 
103     /**
104      * Schedules the stop of recording for a given lane-direction.
105      * @param time time to stop recording
106      * @param kpiLaneDirection lane-direction to stop recording
107      */
108     public abstract void scheduleStopRecording(final Time time, final KpiLaneDirection kpiLaneDirection);
109 
110     /**
111      * Registers meta data types that will be stored with the trajectories.
112      * @param metaDataTypes meta data types to register
113      */
114     public final void registerMetaDataTypes(final Set<MetaDataType<?>> metaDataTypes)
115     {
116         Throw.whenNull(metaDataTypes, "MetaDataTypes may not be null.");
117         this.registeredMetaDataTypes.addAll(metaDataTypes);
118     }
119 
120     /**
121      * Registers extended data type that will be stored with the trajectories.
122      * @param extendedDataType extended data type to register
123      */
124     public final void registerExtendedDataType(final ExtendedDataType<?, ?, ?> extendedDataType)
125     {
126         Throw.whenNull(extendedDataType, "ExtendedDataType may not be null.");
127         this.extendedDataTypes.add(extendedDataType);
128     }
129 
130     /**
131      * Whether this sampler has the given extended data type registered to it.
132      * @param extendedDataType extended data type
133      * @return whether this sampler has the given extended data type registered to it
134      */
135     public boolean contains(final ExtendedDataType<?, ?, ?> extendedDataType)
136     {
137         return this.extendedDataTypes.contains(extendedDataType);
138     }
139 
140     /**
141      * Start recording at the given time (which should be the current time) on the given lane direction.
142      * @param kpiLaneDirection lane direction
143      */
144     public final void startRecording(final KpiLaneDirection kpiLaneDirection)
145     {
146         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
147         if (this.trajectories.containsKey(kpiLaneDirection))
148         {
149             return;
150         }
151         this.trajectories.put(kpiLaneDirection, new TrajectoryGroup(now(), kpiLaneDirection));
152         initRecording(kpiLaneDirection);
153     }
154 
155     /**
156      * Adds listeners to start recording.
157      * @param kpiLaneDirection lane direction to initialize recording for
158      */
159     public abstract void initRecording(final KpiLaneDirection kpiLaneDirection);
160 
161     /**
162      * Stop recording at given lane direction.
163      * @param kpiLaneDirection lane direction
164      */
165     public final void stopRecording(final KpiLaneDirection kpiLaneDirection)
166     {
167         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
168         if (!this.trajectories.containsKey(kpiLaneDirection) || this.endTimes.get(kpiLaneDirection).gt(now()))
169         {
170             return;
171         }
172         finalizeRecording(kpiLaneDirection);
173     }
174 
175     /**
176      * Remove listeners to stop recording.
177      * @param kpiLaneDirection lane direction to finalize recording for
178      */
179     public abstract void finalizeRecording(final KpiLaneDirection kpiLaneDirection);
180 
181     /**
182      * Creates a trajectory with the current snapshot of a GTU.
183      * @param kpiLaneDirection lane direction the gtu is at
184      * @param position position of the gtu on the lane
185      * @param speed speed of the gtu
186      * @param acceleration acceleration of the gtu
187      * @param time current time
188      * @param gtu gtu
189      */
190     public final void processGtuAddEvent(final KpiLaneDirection kpiLaneDirection, final Length position, final Speed speed,
191             final Acceleration acceleration, final Time time, final GtuDataInterface gtu)
192     {
193         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
194         Throw.whenNull(position, "Position may not be null.");
195         Throw.whenNull(speed, "Speed may not be null.");
196         Throw.whenNull(acceleration, "Acceleration may not be null.");
197         Throw.whenNull(time, "Time may not be null.");
198         Throw.whenNull(gtu, "GtuDataInterface may not be null.");
199         if (kpiLaneDirection.getLaneData().getLength().lt(position))
200         {
201             // ignore event if beyond lane length (may happen during lane change)
202             return;
203         }
204         String gtuId = gtu.getId();
205         Trajectory trajectory = new Trajectory(gtu, makeMetaData(gtu), this.extendedDataTypes, kpiLaneDirection);
206         if (!this.trajectoryPerGtu.containsKey(gtuId))
207         {
208             Map<KpiLaneDirection, Trajectory> map = new LinkedHashMap<>();
209             this.trajectoryPerGtu.put(gtuId, map);
210         }
211         this.trajectoryPerGtu.get(gtuId).put(kpiLaneDirection, trajectory);
212         this.trajectories.get(kpiLaneDirection).addTrajectory(trajectory);
213         processGtuMoveEvent(kpiLaneDirection, position, speed, acceleration, time, gtu);
214     }
215 
216     /**
217      * Adds a new snapshot of a GTU to its recording trajectory, if recorded. This method may be invoked on GTU that are not
218      * being recorded; the event will then be ignored.
219      * @param kpiLaneDirection lane direction the gtu is at
220      * @param position position of the gtu on the lane
221      * @param speed speed of the gtu
222      * @param acceleration acceleration of the gtu
223      * @param time current time
224      * @param gtu gtu
225      */
226     public final void processGtuMoveEvent(final KpiLaneDirection kpiLaneDirection, final Length position, final Speed speed,
227             final Acceleration acceleration, final Time time, final GtuDataInterface gtu)
228     {
229         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
230         Throw.whenNull(position, "Position may not be null.");
231         Throw.whenNull(speed, "Speed may not be null.");
232         Throw.whenNull(acceleration, "Acceleration may not be null.");
233         Throw.whenNull(time, "Time may not be null.");
234         Throw.whenNull(gtu, "GtuDataInterface may not be null.");
235         String gtuId = gtu.getId();
236         if (this.trajectoryPerGtu.containsKey(gtuId) && this.trajectoryPerGtu.get(gtuId).containsKey(kpiLaneDirection))
237         {
238             this.trajectoryPerGtu.get(gtuId).get(kpiLaneDirection).add(position, speed, acceleration, time, gtu);
239         }
240     }
241 
242     /**
243      * Finalizes a trajectory with the current snapshot of a GTU.
244      * @param kpiLaneDirection lane direction the gtu is at
245      * @param position position of the gtu on the lane
246      * @param speed speed of the gtu
247      * @param acceleration acceleration of the gtu
248      * @param time current time
249      * @param gtu gtu
250      */
251     public final void processGtuRemoveEvent(final KpiLaneDirection kpiLaneDirection, final Length position, final Speed speed,
252             final Acceleration acceleration, final Time time, final GtuDataInterface gtu)
253     {
254         processGtuMoveEvent(kpiLaneDirection, position, speed, acceleration, time, gtu);
255         processGtuRemoveEvent(kpiLaneDirection, gtu);
256     }
257 
258     /**
259      * Finalizes a trajectory.
260      * @param kpiLaneDirection lane direction the gtu is at
261      * @param gtu gtu
262      */
263     public final void processGtuRemoveEvent(final KpiLaneDirection kpiLaneDirection, final GtuDataInterface gtu)
264     {
265         Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
266         Throw.whenNull(gtu, "GtuDataInterface may not be null.");
267         String gtuId = gtu.getId();
268         if (this.trajectoryPerGtu.containsKey(gtuId))
269         {
270             this.trajectoryPerGtu.get(gtuId).remove(kpiLaneDirection);
271             if (this.trajectoryPerGtu.get(gtuId).isEmpty())
272             {
273                 this.trajectoryPerGtu.remove(gtuId);
274             }
275         }
276     }
277 
278     /**
279      * @param gtu gtu to return meta data for
280      * @param <T> underlying type of a meta data type
281      * @return meta data for the given gtu
282      */
283     @SuppressWarnings("unchecked")
284     private <T> MetaData makeMetaData(final GtuDataInterface gtu)
285     {
286         MetaData metaData = new MetaData();
287         for (MetaDataType<?> metaDataType : this.registeredMetaDataTypes)
288         {
289             T value = (T) metaDataType.getValue(gtu);
290             if (value != null)
291             {
292                 metaData.put((MetaDataType<T>) metaDataType, value);
293             }
294         }
295         return metaData;
296     }
297 
298     /**
299      * Returns whether there is data for the give lane direction.
300      * @param kpiLaneDirection lane direction
301      * @return whether there is data for the give lane direction
302      */
303     public final boolean contains(final KpiLaneDirection kpiLaneDirection)
304     {
305         return this.trajectories.containsKey(kpiLaneDirection);
306     }
307 
308     /**
309      * Returns the trajectory group of given lane direction.
310      * @param kpiLaneDirection lane direction
311      * @return trajectory group of given lane direction, {@code null} if none
312      */
313     public final TrajectoryGroup getTrajectoryGroup(final KpiLaneDirection kpiLaneDirection)
314     {
315         return this.trajectories.get(kpiLaneDirection);
316     }
317 
318     /**
319      * Write the contents of the sampler in to a file. By default this is zipped and numeric data is formated %.3f.
320      * @param file file
321      */
322     public final void writeToFile(final String file)
323     {
324         writeToFile(file, "%.3f", true);
325     }
326 
327     /**
328      * Write the contents of the sampler in to a file.
329      * @param file file
330      * @param format number format, as used in {@code String.format()}
331      * @param zipped whether to zip the file
332      */
333     // TODO This returns all data, regardless of registered space-time regions. We need a query to have space-time regions.
334     public final void writeToFile(String file, final String format, final boolean zipped)
335     {
336         String name = null;
337         if (zipped)
338         {
339             File f = new File(file);
340             name = f.getName();
341             if (!file.endsWith(".zip"))
342             {
343                 file += ".zip";
344             }
345         }
346         int counter = 0;
347         FileOutputStream fos = null;
348         ZipOutputStream zos = null;
349         OutputStreamWriter osw = null;
350         BufferedWriter bw = null;
351         try
352         {
353             fos = new FileOutputStream(file);
354             if (zipped)
355             {
356                 zos = new ZipOutputStream(fos);
357                 zos.putNextEntry(new ZipEntry(name));
358                 osw = new OutputStreamWriter(zos);
359             }
360             else
361             {
362                 osw = new OutputStreamWriter(fos);
363             }
364             bw = new BufferedWriter(osw);
365             // gather all meta data types for the header line
366             List<MetaDataType<?>> allMetaDataTypes = new ArrayList<>();
367             for (KpiLaneDirection kpiLaneDirection : this.trajectories.keySet())
368             {
369                 for (Trajectory trajectory : this.trajectories.get(kpiLaneDirection).getTrajectories())
370                 {
371                     for (MetaDataType<?> metaDataType : trajectory.getMetaDataTypes())
372                     {
373                         if (!allMetaDataTypes.contains(metaDataType))
374                         {
375                             allMetaDataTypes.add(metaDataType);
376                         }
377                     }
378                 }
379             }
380             // gather all extended data types for the header line
381             List<ExtendedDataType<?, ?, ?>> allExtendedDataTypes = new ArrayList<>();
382             for (KpiLaneDirection kpiLaneDirection : this.trajectories.keySet())
383             {
384                 for (Trajectory trajectory : this.trajectories.get(kpiLaneDirection).getTrajectories())
385                 {
386                     for (ExtendedDataType<?, ?, ?> extendedDataType : trajectory.getExtendedDataTypes())
387                     {
388                         if (!allExtendedDataTypes.contains(extendedDataType))
389                         {
390                             allExtendedDataTypes.add(extendedDataType);
391                         }
392                     }
393                 }
394             }
395             // create header line
396             StringBuilder str = new StringBuilder();
397             str.append("traj#,linkId,laneId&dir,gtuId,t,x,v,a");
398             for (MetaDataType<?> metaDataType : allMetaDataTypes)
399             {
400                 str.append(",");
401                 str.append(metaDataType.getId());
402             }
403             for (ExtendedDataType<?, ?, ?> extendedDataType : allExtendedDataTypes)
404             {
405                 str.append(",");
406                 str.append(extendedDataType.getId());
407             }
408             bw.write(str.toString());
409             bw.newLine();
410             for (KpiLaneDirection kpiLaneDirection : this.trajectories.keySet())
411             {
412                 for (Trajectory trajectory : this.trajectories.get(kpiLaneDirection).getTrajectories())
413                 {
414                     counter++;
415                     float[] t = trajectory.getT();
416                     float[] x = trajectory.getX();
417                     float[] v = trajectory.getV();
418                     float[] a = trajectory.getA();
419                     Map<ExtendedDataType<?, ?, ?>, Object> extendedData = new HashMap<>();
420                     for (ExtendedDataType<?, ?, ?> extendedDataType : allExtendedDataTypes)
421                     {
422                         if (trajectory.contains(extendedDataType))
423                         {
424                             try
425                             {
426                                 extendedData.put(extendedDataType, trajectory.getExtendedData(extendedDataType));
427                             }
428                             catch (SamplingException exception)
429                             {
430                                 // should not occur, we obtain the extended data types from the trajectory
431                                 throw new RuntimeException("Error while loading extended data type.", exception);
432                             }
433                         }
434                     }
435                     for (int i = 0; i < t.length; i++)
436                     {
437                         str = new StringBuilder();
438                         str.append(counter);
439                         str.append(",");
440                         if (i == 0)
441                         {
442                             str.append(kpiLaneDirection.getLaneData().getLinkData().getId());
443                             str.append(",");
444                             str.append(kpiLaneDirection.getLaneData().getId());
445                             str.append(kpiLaneDirection.getKpiDirection().isPlus() ? "+" : "-");
446                             str.append(",");
447                             str.append(trajectory.getGtuId());
448                             str.append(",");
449                         }
450                         else
451                         {
452                             // one trajectory is on the same lane and pertains to the same GTU, no need to repeat data
453                             str.append(",,,");
454                         }
455                         str.append(String.format(format, t[i]));
456                         str.append(",");
457                         str.append(String.format(format, x[i]));
458                         str.append(",");
459                         str.append(String.format(format, v[i]));
460                         str.append(",");
461                         str.append(String.format(format, a[i]));
462                         for (MetaDataType<?> metaDataType : allMetaDataTypes)
463                         {
464                             str.append(",");
465                             if (i == 0 && trajectory.contains(metaDataType))
466                             {
467                                 // no need to repeat meta data
468                                 str.append(metaDataType.formatValue(format, castValue(trajectory.getMetaData(metaDataType))));
469                             }
470                         }
471                         for (ExtendedDataType<?, ?, ?> extendedDataType : allExtendedDataTypes)
472                         {
473                             str.append(",");
474                             if (trajectory.contains(extendedDataType))
475                             {
476                                 try
477                                 {
478                                     str.append(
479                                             extendedDataType.formatValue(format, castValue(extendedData, extendedDataType, i)));
480                                 }
481                                 catch (SamplingException exception)
482                                 {
483                                     // should not occur, we obtain the extended data types from the trajectory
484                                     throw new RuntimeException("Error while loading extended data type.", exception);
485                                 }
486                             }
487                         }
488                         bw.write(str.toString());
489                         bw.newLine();
490                     }
491                 }
492             }
493         }
494         catch (IOException exception)
495         {
496             throw new RuntimeException("Could not write to file.", exception);
497         }
498         // close file on fail
499         finally
500         {
501             try
502             {
503                 if (bw != null)
504                 {
505                     bw.close();
506                 }
507                 if (osw != null)
508                 {
509                     osw.close();
510                 }
511                 if (zos != null)
512                 {
513                     zos.close();
514                 }
515                 if (fos != null)
516                 {
517                     fos.close();
518                 }
519             }
520             catch (IOException ex)
521             {
522                 ex.printStackTrace();
523             }
524         }
525     }
526 
527     /**
528      * Cast value to type for meta data.
529      * @param value value object to cast
530      * @return cast value
531      */
532     @SuppressWarnings("unchecked")
533     private <T> T castValue(final Object value)
534     {
535         return (T) value;
536     }
537 
538     /**
539      * Cast value to type for extended data.
540      * @param extendedData extended data of trajectory in output form
541      * @param extendedDataType extended data type
542      * @param i index of value to return
543      * @return cast value
544      * @throws SamplingException when the found index is out of bounds
545      */
546     @SuppressWarnings("unchecked")
547     private <T, O, S> T castValue(final Map<ExtendedDataType<?, ?, ?>, Object> extendedData,
548             final ExtendedDataType<?, ?, ?> extendedDataType, final int i) throws SamplingException
549     {
550         // is only called on value directly taken from an ExtendedDataType within range of trajectory
551         ExtendedDataType<T, O, S> edt = (ExtendedDataType<T, O, S>) extendedDataType;
552         return edt.getOutputValue((O) extendedData.get(edt), i);
553     }
554 
555     /** {@inheritDoc} */
556     @Override
557     public int hashCode()
558     {
559         final int prime = 31;
560         int result = 1;
561         result = prime * result + ((this.endTimes == null) ? 0 : this.endTimes.hashCode());
562         result = prime * result + ((this.extendedDataTypes == null) ? 0 : this.extendedDataTypes.hashCode());
563         result = prime * result + ((this.registeredMetaDataTypes == null) ? 0 : this.registeredMetaDataTypes.hashCode());
564         result = prime * result + ((this.trajectories == null) ? 0 : this.trajectories.hashCode());
565         result = prime * result + ((this.trajectoryPerGtu == null) ? 0 : this.trajectoryPerGtu.hashCode());
566         return result;
567     }
568 
569     /** {@inheritDoc} */
570     @Override
571     public boolean equals(final Object obj)
572     {
573         if (this == obj)
574         {
575             return true;
576         }
577         if (obj == null)
578         {
579             return false;
580         }
581         if (getClass() != obj.getClass())
582         {
583             return false;
584         }
585         Sampler other = (Sampler) obj;
586         if (this.endTimes == null)
587         {
588             if (other.endTimes != null)
589             {
590                 return false;
591             }
592         }
593         else if (!this.endTimes.equals(other.endTimes))
594         {
595             return false;
596         }
597         if (this.extendedDataTypes == null)
598         {
599             if (other.extendedDataTypes != null)
600             {
601                 return false;
602             }
603         }
604         else if (!this.extendedDataTypes.equals(other.extendedDataTypes))
605         {
606             return false;
607         }
608         if (this.registeredMetaDataTypes == null)
609         {
610             if (other.registeredMetaDataTypes != null)
611             {
612                 return false;
613             }
614         }
615         else if (!this.registeredMetaDataTypes.equals(other.registeredMetaDataTypes))
616         {
617             return false;
618         }
619         if (this.trajectories == null)
620         {
621             if (other.trajectories != null)
622             {
623                 return false;
624             }
625         }
626         else if (!this.trajectories.equals(other.trajectories))
627         {
628             return false;
629         }
630         if (this.trajectoryPerGtu == null)
631         {
632             if (other.trajectoryPerGtu != null)
633             {
634                 return false;
635             }
636         }
637         else if (!this.trajectoryPerGtu.equals(other.trajectoryPerGtu))
638         {
639             return false;
640         }
641         return true;
642     }
643 
644 }