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