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  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  public abstract class Sampler<G extends GtuDataInterface>
38  {
39  
40      
41      private final Map<KpiLaneDirection, TrajectoryGroup<G>> trajectories = new LinkedHashMap<>();
42  
43      
44      private final Map<KpiLaneDirection, Time> endTimes = new LinkedHashMap<>();
45  
46      
47      private final Map<String, Map<KpiLaneDirection, Trajectory<G>>> trajectoryPerGtu = new LinkedHashMap<>();
48  
49      
50      private final Set<ExtendedDataType<?, ?, ?, G>> extendedDataTypes = new LinkedHashSet<>();
51  
52      
53      private Set<MetaDataType<?>> registeredMetaDataTypes = new LinkedHashSet<>();
54  
55      
56      private Set<SpaceTimeRegion> spaceTimeRegions = new LinkedHashSet<>();
57  
58      
59  
60  
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  
93  
94  
95      public abstract Time now();
96  
97      
98  
99  
100 
101 
102     public abstract void scheduleStartRecording(final Time time, final KpiLaneDirection kpiLaneDirection);
103 
104     
105 
106 
107 
108 
109     public abstract void scheduleStopRecording(final Time time, final KpiLaneDirection kpiLaneDirection);
110 
111     
112 
113 
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 
123 
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 
133 
134 
135 
136     public boolean contains(final ExtendedDataType<?, ?, ?, ?> extendedDataType)
137     {
138         return this.extendedDataTypes.contains(extendedDataType);
139     }
140 
141     
142 
143 
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 
158 
159 
160     public abstract void initRecording(final KpiLaneDirection kpiLaneDirection);
161 
162     
163 
164 
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 
178 
179 
180     public abstract void finalizeRecording(final KpiLaneDirection kpiLaneDirection);
181 
182     
183 
184 
185 
186 
187 
188 
189 
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             
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 
219 
220 
221 
222 
223 
224 
225 
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 
245 
246 
247 
248 
249 
250 
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 
261 
262 
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 
281 
282 
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 
301 
302 
303 
304     public final boolean contains(final KpiLaneDirection kpiLaneDirection)
305     {
306         return this.trajectories.containsKey(kpiLaneDirection);
307     }
308 
309     
310 
311 
312 
313 
314     public final TrajectoryGroup<G> getTrajectoryGroup(final KpiLaneDirection kpiLaneDirection)
315     {
316         return this.trajectories.get(kpiLaneDirection);
317     }
318 
319     
320 
321 
322 
323     public final void writeToFile(final String file)
324     {
325         writeToFile(file, "%.3f", CompressionMethod.ZIP);
326     }
327 
328     
329 
330 
331 
332 
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         
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             
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             
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             
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                                 
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                             
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                                 
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                                     
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         
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 
500 
501 
502 
503     @SuppressWarnings("unchecked")
504     private <T> T castValue(final Object value)
505     {
506         return (T) value;
507     }
508 
509     
510 
511 
512 
513 
514 
515 
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         
522         ExtendedDataType<T, O, S, ?> edt = (ExtendedDataType<T, O, S, ?>) extendedDataType;
523         return edt.getOutputValue((O) extendedData.get(edt), i);
524     }
525 
526     
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     
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 
617 
618 
619 
620 
621 
622 
623 
624 
625 
626 
627     public enum CompressionMethod
628     {
629         
630         NONE,
631 
632         
633         OMIT_DUPLICATE_INFO,
634 
635         
636         ZIP,
637 
638     }
639 
640 }