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
31
32
33
34
35
36
37
38
39
40 public abstract class Sampler
41 {
42
43
44 private final Map<KpiLaneDirection, TrajectoryGroup> trajectories = new LinkedHashMap<>();
45
46
47 private final Map<KpiLaneDirection, Time> endTimes = new LinkedHashMap<>();
48
49
50 private final Map<String, Map<KpiLaneDirection, Trajectory>> trajectoryPerGtu = new LinkedHashMap<>();
51
52
53 private final Set<ExtendedDataType<?, ?, ?>> extendedDataTypes = new LinkedHashSet<>();
54
55
56 private Set<MetaDataType<?>> registeredMetaDataTypes = 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 }
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<?, ?, ?> 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 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
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
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 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
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 GtuDataInterface 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 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
280
281
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
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 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", true);
325 }
326
327
328
329
330
331
332
333
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
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
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
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
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
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
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
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
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
529
530
531
532 @SuppressWarnings("unchecked")
533 private <T> T castValue(final Object value)
534 {
535 return (T) value;
536 }
537
538
539
540
541
542
543
544
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
551 ExtendedDataType<T, O, S> edt = (ExtendedDataType<T, O, S>) extendedDataType;
552 return edt.getOutputValue((O) extendedData.get(edt), i);
553 }
554
555
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
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 }