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 }