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 }