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