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
335 public final void writeToFile(String file, final String format, final boolean zipped)
336 {
337 String name = null;
338 if (zipped)
339 {
340 File f = new File(file);
341 name = f.getName();
342 if (!file.endsWith(".zip"))
343 {
344 file += ".zip";
345 }
346 }
347 int counter = 0;
348 FileOutputStream fos = null;
349 ZipOutputStream zos = null;
350 OutputStreamWriter osw = null;
351 BufferedWriter bw = null;
352 try
353 {
354 fos = new FileOutputStream(file);
355 if (zipped)
356 {
357 zos = new ZipOutputStream(fos);
358 zos.putNextEntry(new ZipEntry(name));
359 osw = new OutputStreamWriter(zos);
360 }
361 else
362 {
363 osw = new OutputStreamWriter(fos);
364 }
365 bw = new BufferedWriter(osw);
366
367 List<MetaDataType<?>> allMetaDataTypes = new ArrayList<>();
368 for (KpiLaneDirection kpiLaneDirection : this.trajectories.keySet())
369 {
370 for (Trajectory trajectory : this.trajectories.get(kpiLaneDirection).getTrajectories())
371 {
372 for (MetaDataType<?> metaDataType : trajectory.getMetaDataTypes())
373 {
374 if (!allMetaDataTypes.contains(metaDataType))
375 {
376 allMetaDataTypes.add(metaDataType);
377 }
378 }
379 }
380 }
381
382 List<ExtendedDataType<?, ?, ?>> allExtendedDataTypes = new ArrayList<>();
383 for (KpiLaneDirection kpiLaneDirection : this.trajectories.keySet())
384 {
385 for (Trajectory trajectory : this.trajectories.get(kpiLaneDirection).getTrajectories())
386 {
387 for (ExtendedDataType<?, ?, ?> extendedDataType : trajectory.getExtendedDataTypes())
388 {
389 if (!allExtendedDataTypes.contains(extendedDataType))
390 {
391 allExtendedDataTypes.add(extendedDataType);
392 }
393 }
394 }
395 }
396
397 StringBuilder str = new StringBuilder();
398 str.append("traj#,linkId,laneId&dir,gtuId,t,x,v,a");
399 for (MetaDataType<?> metaDataType : allMetaDataTypes)
400 {
401 str.append(",");
402 str.append(metaDataType.getId());
403 }
404 for (ExtendedDataType<?, ?, ?> extendedDataType : allExtendedDataTypes)
405 {
406 str.append(",");
407 str.append(extendedDataType.getId());
408 }
409 bw.write(str.toString());
410 bw.newLine();
411 for (KpiLaneDirection kpiLaneDirection : this.trajectories.keySet())
412 {
413 for (Trajectory trajectory : this.trajectories.get(kpiLaneDirection).getTrajectories())
414 {
415 counter++;
416 float[] t = trajectory.getT();
417 float[] x = trajectory.getX();
418 float[] v = trajectory.getV();
419 float[] a = trajectory.getA();
420 Map<ExtendedDataType<?, ?, ?>, Object> extendedData = new HashMap<>();
421 for (ExtendedDataType<?, ?, ?> extendedDataType : allExtendedDataTypes)
422 {
423 if (trajectory.contains(extendedDataType))
424 {
425 try
426 {
427 extendedData.put(extendedDataType, trajectory.getExtendedData(extendedDataType));
428 }
429 catch (SamplingException exception)
430 {
431
432 throw new RuntimeException("Error while loading extended data type.", exception);
433 }
434 }
435 }
436 for (int i = 0; i < t.length; i++)
437 {
438 str = new StringBuilder();
439 str.append(counter);
440 str.append(",");
441 if (i == 0)
442 {
443 str.append(kpiLaneDirection.getLaneData().getLinkData().getId());
444 str.append(",");
445 str.append(kpiLaneDirection.getLaneData().getId());
446 str.append(kpiLaneDirection.getKpiDirection().isPlus() ? "+" : "-");
447 str.append(",");
448 str.append(trajectory.getGtuId());
449 str.append(",");
450 }
451 else
452 {
453
454 str.append(",,,");
455 }
456 str.append(String.format(format, t[i]));
457 str.append(",");
458 str.append(String.format(format, x[i]));
459 str.append(",");
460 str.append(String.format(format, v[i]));
461 str.append(",");
462 str.append(String.format(format, a[i]));
463 for (MetaDataType<?> metaDataType : allMetaDataTypes)
464 {
465 str.append(",");
466 if (i == 0 && trajectory.contains(metaDataType))
467 {
468
469 str.append(metaDataType.formatValue(format, castValue(trajectory.getMetaData(metaDataType))));
470 }
471 }
472 for (ExtendedDataType<?, ?, ?> extendedDataType : allExtendedDataTypes)
473 {
474 str.append(",");
475 if (trajectory.contains(extendedDataType))
476 {
477 try
478 {
479 str.append(
480 extendedDataType.formatValue(format, castValue(extendedData, extendedDataType, i)));
481 }
482 catch (SamplingException exception)
483 {
484
485 throw new RuntimeException("Error while loading extended data type.", exception);
486 }
487 }
488 }
489 bw.write(str.toString());
490 bw.newLine();
491 }
492 }
493 }
494 }
495 catch (IOException exception)
496 {
497 throw new RuntimeException("Could not write to file.", exception);
498 }
499
500 finally
501 {
502 try
503 {
504 if (bw != null)
505 {
506 bw.close();
507 }
508 if (osw != null)
509 {
510 osw.close();
511 }
512 if (zos != null)
513 {
514 zos.close();
515 }
516 if (fos != null)
517 {
518 fos.close();
519 }
520 }
521 catch (IOException ex)
522 {
523 ex.printStackTrace();
524 }
525 }
526 }
527
528
529
530
531
532
533 @SuppressWarnings("unchecked")
534 private <T> T castValue(final Object value)
535 {
536 return (T) value;
537 }
538
539
540
541
542
543
544
545
546
547 @SuppressWarnings("unchecked")
548 private <T, O, S> T castValue(final Map<ExtendedDataType<?, ?, ?>, Object> extendedData,
549 final ExtendedDataType<?, ?, ?> extendedDataType, final int i) throws SamplingException
550 {
551
552 ExtendedDataType<T, O, S> edt = (ExtendedDataType<T, O, S>) extendedDataType;
553 return edt.getOutputValue((O) extendedData.get(edt), i);
554 }
555
556
557 @Override
558 public int hashCode()
559 {
560 final int prime = 31;
561 int result = 1;
562 result = prime * result + ((this.endTimes == null) ? 0 : this.endTimes.hashCode());
563 result = prime * result + ((this.extendedDataTypes == null) ? 0 : this.extendedDataTypes.hashCode());
564 result = prime * result + ((this.registeredMetaDataTypes == null) ? 0 : this.registeredMetaDataTypes.hashCode());
565 result = prime * result + ((this.trajectories == null) ? 0 : this.trajectories.hashCode());
566 result = prime * result + ((this.trajectoryPerGtu == null) ? 0 : this.trajectoryPerGtu.hashCode());
567 return result;
568 }
569
570
571 @Override
572 public boolean equals(final Object obj)
573 {
574 if (this == obj)
575 {
576 return true;
577 }
578 if (obj == null)
579 {
580 return false;
581 }
582 if (getClass() != obj.getClass())
583 {
584 return false;
585 }
586 Sampler other = (Sampler) obj;
587 if (this.endTimes == null)
588 {
589 if (other.endTimes != null)
590 {
591 return false;
592 }
593 }
594 else if (!this.endTimes.equals(other.endTimes))
595 {
596 return false;
597 }
598 if (this.extendedDataTypes == null)
599 {
600 if (other.extendedDataTypes != null)
601 {
602 return false;
603 }
604 }
605 else if (!this.extendedDataTypes.equals(other.extendedDataTypes))
606 {
607 return false;
608 }
609 if (this.registeredMetaDataTypes == null)
610 {
611 if (other.registeredMetaDataTypes != null)
612 {
613 return false;
614 }
615 }
616 else if (!this.registeredMetaDataTypes.equals(other.registeredMetaDataTypes))
617 {
618 return false;
619 }
620 if (this.trajectories == null)
621 {
622 if (other.trajectories != null)
623 {
624 return false;
625 }
626 }
627 else if (!this.trajectories.equals(other.trajectories))
628 {
629 return false;
630 }
631 if (this.trajectoryPerGtu == null)
632 {
633 if (other.trajectoryPerGtu != null)
634 {
635 return false;
636 }
637 }
638 else if (!this.trajectoryPerGtu.equals(other.trajectoryPerGtu))
639 {
640 return false;
641 }
642 return true;
643 }
644
645 }