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.LinkedHashMap;
7   import java.util.LinkedHashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import org.djunits.value.vdouble.scalar.Acceleration;
13  import org.djunits.value.vdouble.scalar.Length;
14  import org.djunits.value.vdouble.scalar.Speed;
15  import org.djunits.value.vdouble.scalar.Time;
16  import org.djutils.exceptions.Throw;
17  import org.opentrafficsim.base.CompressedFileWriter;
18  import org.opentrafficsim.kpi.interfaces.GtuDataInterface;
19  import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
20  import org.opentrafficsim.kpi.sampling.meta.MetaData;
21  import org.opentrafficsim.kpi.sampling.meta.MetaDataSet;
22  import org.opentrafficsim.kpi.sampling.meta.MetaDataType;
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  public abstract class Sampler<G extends GtuDataInterface>
37  {
38  
39      
40      private final Map<KpiLaneDirection, TrajectoryGroup<G>> trajectories = new LinkedHashMap<>();
41  
42      
43      private final Map<KpiLaneDirection, Time> endTimes = new LinkedHashMap<>();
44  
45      
46      private final Map<String, Map<KpiLaneDirection, Trajectory<G>>> trajectoryPerGtu = new LinkedHashMap<>();
47  
48      
49      private final Set<ExtendedDataType<?, ?, ?, G>> extendedDataTypes = new LinkedHashSet<>();
50  
51      
52      private Set<MetaDataType<?>> registeredMetaDataTypes = new LinkedHashSet<>();
53  
54      
55      private Set<SpaceTimeRegion> spaceTimeRegions = new LinkedHashSet<>();
56  
57      
58  
59  
60  
61      public final void registerSpaceTimeRegion(final SpaceTimeRegion spaceTimeRegion)
62      {
63          Throw.whenNull(spaceTimeRegion, "SpaceTimeRegion may not be null.");
64          Time firstPossibleDataTime;
65          if (this.trajectories.containsKey(spaceTimeRegion.getLaneDirection()))
66          {
67              firstPossibleDataTime = this.trajectories.get(spaceTimeRegion.getLaneDirection()).getStartTime();
68          }
69          else
70          {
71              firstPossibleDataTime = now();
72          }
73          Throw.when(spaceTimeRegion.getStartTime().lt(firstPossibleDataTime), IllegalStateException.class,
74                  "Space time region with start time %s is defined while data is available from %s onwards.",
75                  spaceTimeRegion.getStartTime(), firstPossibleDataTime);
76          if (this.trajectories.containsKey(spaceTimeRegion.getLaneDirection()))
77          {
78              this.endTimes.put(spaceTimeRegion.getLaneDirection(),
79                      Time.max(this.endTimes.get(spaceTimeRegion.getLaneDirection()), spaceTimeRegion.getEndTime()));
80          }
81          else
82          {
83              this.endTimes.put(spaceTimeRegion.getLaneDirection(), spaceTimeRegion.getEndTime());
84              scheduleStartRecording(spaceTimeRegion.getStartTime(), spaceTimeRegion.getLaneDirection());
85          }
86          scheduleStopRecording(this.endTimes.get(spaceTimeRegion.getLaneDirection()), spaceTimeRegion.getLaneDirection());
87          this.spaceTimeRegions.add(spaceTimeRegion);
88      }
89  
90      
91  
92  
93  
94      public abstract Time now();
95  
96      
97  
98  
99  
100 
101     public abstract void scheduleStartRecording(final Time time, final KpiLaneDirection kpiLaneDirection);
102 
103     
104 
105 
106 
107 
108     public abstract void scheduleStopRecording(final Time time, final KpiLaneDirection kpiLaneDirection);
109 
110     
111 
112 
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 
122 
123 
124     public final void registerExtendedDataType(final ExtendedDataType<?, ?, ?, G> extendedDataType)
125     {
126         Throw.whenNull(extendedDataType, "ExtendedDataType may not be null.");
127         this.extendedDataTypes.add(extendedDataType);
128     }
129 
130     
131 
132 
133 
134 
135     public boolean contains(final ExtendedDataType<?, ?, ?, ?> extendedDataType)
136     {
137         return this.extendedDataTypes.contains(extendedDataType);
138     }
139 
140     
141 
142 
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 
157 
158 
159     public abstract void initRecording(final KpiLaneDirection kpiLaneDirection);
160 
161     
162 
163 
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 
177 
178 
179     public abstract void finalizeRecording(final KpiLaneDirection kpiLaneDirection);
180 
181     
182 
183 
184 
185 
186 
187 
188 
189 
190     public final void processGtuAddEvent(final KpiLaneDirection kpiLaneDirection, final Length position, final Speed speed,
191             final Acceleration acceleration, final Time time, final G 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             
202             return;
203         }
204         String gtuId = gtu.getId();
205         Trajectory<G> trajectory = new Trajectory<>(gtu, makeMetaData(gtu), this.extendedDataTypes, kpiLaneDirection);
206         if (!this.trajectoryPerGtu.containsKey(gtuId))
207         {
208             Map<KpiLaneDirection, Trajectory<G>> 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 
218 
219 
220 
221 
222 
223 
224 
225 
226     public final void processGtuMoveEvent(final KpiLaneDirection kpiLaneDirection, final Length position, final Speed speed,
227             final Acceleration acceleration, final Time time, final G 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 
244 
245 
246 
247 
248 
249 
250 
251     public final void processGtuRemoveEvent(final KpiLaneDirection kpiLaneDirection, final Length position, final Speed speed,
252             final Acceleration acceleration, final Time time, final G gtu)
253     {
254         processGtuMoveEvent(kpiLaneDirection, position, speed, acceleration, time, gtu);
255         processGtuRemoveEvent(kpiLaneDirection, gtu);
256     }
257 
258     
259 
260 
261 
262 
263     public final void processGtuRemoveEvent(final KpiLaneDirection kpiLaneDirection, final G 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 
280 
281 
282 
283     @SuppressWarnings("unchecked")
284     private <T> MetaData makeMetaData(final G gtu)
285     {
286         MetaDatameta/MetaData.html#MetaData">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 
300 
301 
302 
303     public final boolean contains(final KpiLaneDirection kpiLaneDirection)
304     {
305         return this.trajectories.containsKey(kpiLaneDirection);
306     }
307 
308     
309 
310 
311 
312 
313     public final TrajectoryGroup<G> getTrajectoryGroup(final KpiLaneDirection kpiLaneDirection)
314     {
315         return this.trajectories.get(kpiLaneDirection);
316     }
317 
318     
319 
320 
321 
322     public final void writeToFile(final String file)
323     {
324         writeToFile(file, "%.3f", CompressionMethod.ZIP);
325     }
326 
327     
328 
329 
330 
331 
332 
333     public final void writeToFile(final String file, final String format, final CompressionMethod compression)
334     {
335         int counter = 0;
336         BufferedWriter bw = CompressedFileWriter.create(file, compression.equals(CompressionMethod.ZIP));
337         
338         Query<G> query = new Query<>(this, "", new MetaDataSet());
339         for (SpaceTimeRegion str : this.spaceTimeRegions)
340         {
341             query.addSpaceTimeRegion(str.getLaneDirection(), str.getStartPosition(), str.getEndPosition(), str.getStartTime(),
342                     str.getEndTime());
343         }
344         List<TrajectoryGroup<G>> groups = query.getTrajectoryGroups(Time.instantiateSI(Double.POSITIVE_INFINITY));
345         try
346         {
347             
348             List<MetaDataType<?>> allMetaDataTypes = new ArrayList<>();
349             for (TrajectoryGroup<G> group : groups)
350             {
351                 for (Trajectory<G> trajectory : group.getTrajectories())
352                 {
353                     for (MetaDataType<?> metaDataType : trajectory.getMetaDataTypes())
354                     {
355                         if (!allMetaDataTypes.contains(metaDataType))
356                         {
357                             allMetaDataTypes.add(metaDataType);
358                         }
359                     }
360                 }
361             }
362             
363             List<ExtendedDataType<?, ?, ?, ?>> allExtendedDataTypes = new ArrayList<>();
364             for (TrajectoryGroup<G> group : groups)
365             {
366                 for (Trajectory<?> trajectory : group.getTrajectories())
367                 {
368                     for (ExtendedDataType<?, ?, ?, ?> extendedDataType : trajectory.getExtendedDataTypes())
369                     {
370                         if (!allExtendedDataTypes.contains(extendedDataType))
371                         {
372                             allExtendedDataTypes.add(extendedDataType);
373                         }
374                     }
375                 }
376             }
377             
378             StringBuilder str = new StringBuilder();
379             str.append("traj#,linkId,laneId&dir,gtuId,t,x,v,a");
380             for (MetaDataType<?> metaDataType : allMetaDataTypes)
381             {
382                 str.append(",");
383                 str.append(metaDataType.getId());
384             }
385             for (ExtendedDataType<?, ?, ?, ?> extendedDataType : allExtendedDataTypes)
386             {
387                 str.append(",");
388                 str.append(extendedDataType.getId());
389             }
390             bw.write(str.toString());
391             bw.newLine();
392             for (TrajectoryGroup<G> group : groups)
393             {
394                 for (Trajectory<G> trajectory : group.getTrajectories())
395                 {
396                     counter++;
397                     float[] t = trajectory.getT();
398                     float[] x = trajectory.getX();
399                     float[] v = trajectory.getV();
400                     float[] a = trajectory.getA();
401                     Map<ExtendedDataType<?, ?, ?, ?>, Object> extendedData = new LinkedHashMap<>();
402                     for (ExtendedDataType<?, ?, ?, ?> extendedDataType : allExtendedDataTypes)
403                     {
404                         if (trajectory.contains(extendedDataType))
405                         {
406                             try
407                             {
408                                 extendedData.put(extendedDataType, trajectory.getExtendedData(extendedDataType));
409                             }
410                             catch (SamplingException exception)
411                             {
412                                 
413                                 throw new RuntimeException("Error while loading extended data type.", exception);
414                             }
415                         }
416                     }
417                     for (int i = 0; i < t.length; i++)
418                     {
419                         str = new StringBuilder();
420                         str.append(counter);
421                         str.append(",");
422                         if (!compression.equals(CompressionMethod.OMIT_DUPLICATE_INFO) || i == 0)
423                         {
424                             str.append(group.getLaneDirection().getLaneData().getLinkData().getId());
425                             str.append(",");
426                             str.append(group.getLaneDirection().getLaneData().getId());
427                             str.append(group.getLaneDirection().getKpiDirection().isPlus() ? "+" : "-");
428                             str.append(",");
429                             str.append(trajectory.getGtuId());
430                             str.append(",");
431                         }
432                         else
433                         {
434                             
435                             str.append(",,,");
436                         }
437                         str.append(String.format(format, t[i]));
438                         str.append(",");
439                         str.append(String.format(format, x[i]));
440                         str.append(",");
441                         str.append(String.format(format, v[i]));
442                         str.append(",");
443                         str.append(String.format(format, a[i]));
444                         for (MetaDataType<?> metaDataType : allMetaDataTypes)
445                         {
446                             str.append(",");
447                             if (i == 0 && trajectory.contains(metaDataType))
448                             {
449                                 
450                                 str.append(metaDataType.formatValue(format, castValue(trajectory.getMetaData(metaDataType))));
451                             }
452                         }
453                         for (ExtendedDataType<?, ?, ?, ?> extendedDataType : allExtendedDataTypes)
454                         {
455                             str.append(",");
456                             if (trajectory.contains(extendedDataType))
457                             {
458                                 try
459                                 {
460                                     str.append(
461                                             extendedDataType.formatValue(format, castValue(extendedData, extendedDataType, i)));
462                                 }
463                                 catch (SamplingException exception)
464                                 {
465                                     
466                                     throw new RuntimeException("Error while loading extended data type.", exception);
467                                 }
468                             }
469                         }
470                         bw.write(str.toString());
471                         bw.newLine();
472                     }
473                 }
474             }
475         }
476         catch (IOException exception)
477         {
478             throw new RuntimeException("Could not write to file.", exception);
479         }
480         
481         finally
482         {
483             try
484             {
485                 if (bw != null)
486                 {
487                     bw.close();
488                 }
489             }
490             catch (IOException ex)
491             {
492                 ex.printStackTrace();
493             }
494         }
495     }
496 
497     
498 
499 
500 
501 
502     @SuppressWarnings("unchecked")
503     private <T> T castValue(final Object value)
504     {
505         return (T) value;
506     }
507 
508     
509 
510 
511 
512 
513 
514 
515 
516     @SuppressWarnings("unchecked")
517     private <T, O, S> T castValue(final Map<ExtendedDataType<?, ?, ?, ?>, Object> extendedData,
518             final ExtendedDataType<?, ?, ?, ?> extendedDataType, final int i) throws SamplingException
519     {
520         
521         ExtendedDataType<T, O, S, ?> edt = (ExtendedDataType<T, O, S, ?>) extendedDataType;
522         return edt.getOutputValue((O) extendedData.get(edt), i);
523     }
524 
525     
526     @Override
527     public int hashCode()
528     {
529         final int prime = 31;
530         int result = 1;
531         result = prime * result + ((this.endTimes == null) ? 0 : this.endTimes.hashCode());
532         result = prime * result + ((this.extendedDataTypes == null) ? 0 : this.extendedDataTypes.hashCode());
533         result = prime * result + ((this.registeredMetaDataTypes == null) ? 0 : this.registeredMetaDataTypes.hashCode());
534         result = prime * result + ((this.trajectories == null) ? 0 : this.trajectories.hashCode());
535         result = prime * result + ((this.trajectoryPerGtu == null) ? 0 : this.trajectoryPerGtu.hashCode());
536         return result;
537     }
538 
539     
540     @Override
541     public boolean equals(final Object obj)
542     {
543         if (this == obj)
544         {
545             return true;
546         }
547         if (obj == null)
548         {
549             return false;
550         }
551         if (getClass() != obj.getClass())
552         {
553             return false;
554         }
555         Sampler<?> other = (Sampler<?>) obj;
556         if (this.endTimes == null)
557         {
558             if (other.endTimes != null)
559             {
560                 return false;
561             }
562         }
563         else if (!this.endTimes.equals(other.endTimes))
564         {
565             return false;
566         }
567         if (this.extendedDataTypes == null)
568         {
569             if (other.extendedDataTypes != null)
570             {
571                 return false;
572             }
573         }
574         else if (!this.extendedDataTypes.equals(other.extendedDataTypes))
575         {
576             return false;
577         }
578         if (this.registeredMetaDataTypes == null)
579         {
580             if (other.registeredMetaDataTypes != null)
581             {
582                 return false;
583             }
584         }
585         else if (!this.registeredMetaDataTypes.equals(other.registeredMetaDataTypes))
586         {
587             return false;
588         }
589         if (this.trajectories == null)
590         {
591             if (other.trajectories != null)
592             {
593                 return false;
594             }
595         }
596         else if (!this.trajectories.equals(other.trajectories))
597         {
598             return false;
599         }
600         if (this.trajectoryPerGtu == null)
601         {
602             if (other.trajectoryPerGtu != null)
603             {
604                 return false;
605             }
606         }
607         else if (!this.trajectoryPerGtu.equals(other.trajectoryPerGtu))
608         {
609             return false;
610         }
611         return true;
612     }
613 
614     
615 
616 
617 
618 
619 
620 
621 
622 
623 
624 
625 
626     public enum CompressionMethod
627     {
628         
629         NONE,
630 
631         
632         OMIT_DUPLICATE_INFO,
633 
634         
635         ZIP,
636 
637     }
638 
639 }