1 package org.opentrafficsim.kpi.sampling;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.lang.reflect.Field;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.ConcurrentModificationException;
10 import java.util.Iterator;
11 import java.util.LinkedHashMap;
12 import java.util.LinkedHashSet;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Map.Entry;
16 import java.util.NoSuchElementException;
17 import java.util.Set;
18 import java.util.stream.IntStream;
19
20 import org.djunits.unit.AccelerationUnit;
21 import org.djunits.unit.DurationUnit;
22 import org.djunits.unit.LengthUnit;
23 import org.djunits.unit.SpeedUnit;
24 import org.djunits.unit.Unit;
25 import org.djunits.value.base.Scalar;
26 import org.djunits.value.vfloat.scalar.FloatAcceleration;
27 import org.djunits.value.vfloat.scalar.FloatDuration;
28 import org.djunits.value.vfloat.scalar.FloatLength;
29 import org.djunits.value.vfloat.scalar.FloatSpeed;
30 import org.djutils.data.Column;
31 import org.djutils.data.Row;
32 import org.djutils.data.Table;
33 import org.djutils.data.csv.CsvData;
34 import org.djutils.data.serialization.TextSerializationException;
35 import org.djutils.exceptions.Throw;
36 import org.djutils.io.CompressedFileWriter;
37 import org.opentrafficsim.kpi.interfaces.GtuData;
38 import org.opentrafficsim.kpi.interfaces.LaneData;
39 import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
40 import org.opentrafficsim.kpi.sampling.meta.FilterDataType;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class SamplerData<G extends GtuData> extends Table
56 {
57
58
59 private static Collection<Column<?>> BASE_COLUMNS = new LinkedHashSet<>();
60
61
62 private final List<ExtendedDataType<?, ?, ?, G>> extendedDataTypes;
63
64
65 private final List<FilterDataType<?>> filterDataTypes;
66
67
68 private final Map<LaneData<?>, TrajectoryGroup<G>> trajectories = new LinkedHashMap<>();
69
70 static
71 {
72 BASE_COLUMNS.add(new Column<>("traj#", "Trajectory number", Integer.class, null));
73 BASE_COLUMNS.add(new Column<>("linkId", "Link id", String.class, null));
74 BASE_COLUMNS.add(new Column<>("laneId", "Lane id", String.class, null));
75 BASE_COLUMNS.add(new Column<>("gtuId", "GTU id", String.class, null));
76 BASE_COLUMNS.add(new Column<>("t", "Simulation time", FloatDuration.class, DurationUnit.SI.getId()));
77 BASE_COLUMNS.add(new Column<>("x", "Position on the lane", FloatLength.class, LengthUnit.SI.getId()));
78 BASE_COLUMNS.add(new Column<>("v", "Speed", FloatSpeed.class, SpeedUnit.SI.getId()));
79 BASE_COLUMNS.add(new Column<>("a", "Acceleration", FloatAcceleration.class, AccelerationUnit.SI.getId()));
80 }
81
82
83
84
85
86
87 public SamplerData(final Set<ExtendedDataType<?, ?, ?, G>> extendedDataTypes, final Set<FilterDataType<?>> filterDataTypes)
88 {
89 super("sampler", "Trajectory data", generateColumns(extendedDataTypes, filterDataTypes));
90
91
92
93
94 this.extendedDataTypes = new ArrayList<>(extendedDataTypes.size());
95 for (int i = BASE_COLUMNS.size(); i < BASE_COLUMNS.size() + extendedDataTypes.size(); i++)
96 {
97 String columnId = getColumn(i).getId();
98 for (ExtendedDataType<?, ?, ?, G> extendedDataType : extendedDataTypes)
99 {
100 if (extendedDataType.getId().equals(columnId))
101 {
102 this.extendedDataTypes.add(extendedDataType);
103 }
104 }
105 }
106 this.filterDataTypes = new ArrayList<>(filterDataTypes.size());
107 for (int i = BASE_COLUMNS.size() + extendedDataTypes.size(); i < BASE_COLUMNS.size() + extendedDataTypes.size()
108 + filterDataTypes.size(); i++)
109 {
110 String columnId = getColumn(i).getId();
111 for (FilterDataType<?> filterType : filterDataTypes)
112 {
113 if (filterType.getId().equals(columnId))
114 {
115 this.filterDataTypes.add(filterType);
116 }
117 }
118 }
119 }
120
121
122
123
124
125
126
127 private static Collection<Column<?>> generateColumns(
128 final Set<? extends ExtendedDataType<?, ?, ?, ? extends GtuData>> extendedDataTypes,
129 final Set<FilterDataType<?>> filterDataTypes)
130 {
131 Collection<Column<?>> out = new ArrayList<>(BASE_COLUMNS.size() + extendedDataTypes.size() + filterDataTypes.size());
132 out.addAll(BASE_COLUMNS);
133 for (ExtendedDataType<?, ?, ?, ?> extendedDataType : extendedDataTypes)
134 {
135 out.add(new Column<>(extendedDataType.getId(), extendedDataType.getDescription(), extendedDataType.getType(),
136 getUnit(extendedDataType)));
137 }
138 for (FilterDataType<?> filterDataType : filterDataTypes)
139 {
140 out.add(new Column<>(filterDataType.getId(), filterDataType.getDescription(), String.class, null));
141 }
142 return out;
143 }
144
145
146
147
148
149
150 private static String getUnit(final ExtendedDataType<?, ?, ?, ?> extendedDataType)
151 {
152 if (Scalar.class.isAssignableFrom(extendedDataType.getType()))
153 {
154 try
155 {
156 Class<?> unitClass = Class.forName(
157 "org.djunits.unit." + extendedDataType.getType().getSimpleName().replace("Float", "") + "Unit");
158 Field field = null;
159 for (Field f : unitClass.getFields())
160 {
161 if (f.getName().equals("SI"))
162 {
163 field = f;
164 break;
165 }
166 else if (f.getName().equals("DEFAULT"))
167 {
168 field = f;
169 }
170 }
171 return field == null ? null : ((Unit<?>) field.get(unitClass)).getId();
172 }
173 catch (ClassNotFoundException | IllegalArgumentException | IllegalAccessException | SecurityException exception)
174 {
175 return null;
176 }
177 }
178 return null;
179 }
180
181
182
183
184
185
186 protected final void putTrajectoryGroup(final LaneData<?> lane, final TrajectoryGroup<G> trajectoryGroup)
187 {
188 this.trajectories.put(lane, trajectoryGroup);
189 }
190
191
192
193
194
195 public final Set<LaneData<?>> getLanes()
196 {
197 return this.trajectories.keySet();
198 }
199
200
201
202
203
204
205 public final boolean contains(final LaneData<?> lane)
206 {
207 return this.trajectories.containsKey(lane);
208 }
209
210
211
212
213
214
215 public final TrajectoryGroup<G> getTrajectoryGroup(final LaneData<?> lane)
216 {
217 return this.trajectories.get(lane);
218 }
219
220
221
222
223
224 public final void writeToFile(final String file)
225 {
226 writeToFile(file, Compression.ZIP);
227 }
228
229
230
231
232
233
234 public final void writeToFile(final String file, final Compression compression)
235 {
236 try
237 {
238 if (compression.equals(Compression.ZIP))
239 {
240 String name = new File(file).getName();
241 String csvName = name.toLowerCase().endsWith(".zip") ? name.substring(0, name.length() - 4) : name;
242 CsvData.writeZippedData(new CompressedFileWriter(file), csvName, csvName + ".header", this);
243 }
244 else
245 {
246 CsvData.writeData(file, file + ".header", this);
247 }
248 }
249 catch (IOException | TextSerializationException exception)
250 {
251 throw new RuntimeException("Unable to write sampler data.", exception);
252 }
253 }
254
255
256 @Override
257 public Iterator<Row> iterator()
258 {
259 return new SamplerDataIterator();
260 }
261
262
263 @Override
264 public boolean isEmpty()
265 {
266 for (TrajectoryGroup<G> group : this.trajectories.values())
267 {
268 for (Trajectory<G> trajectory : group.getTrajectories())
269 {
270 if (trajectory.size() > 0)
271 {
272 return false;
273 }
274 }
275 }
276 return true;
277 }
278
279
280
281
282
283
284
285
286
287
288
289
290 private final class SamplerDataIterator implements Iterator<Row>
291 {
292
293 private Iterator<Entry<LaneData<?>, TrajectoryGroup<G>>> laneIterator =
294 SamplerData.this.trajectories.entrySet().iterator();
295
296
297 private LaneData<?> currentLane;
298
299
300 private Iterator<Trajectory<G>> trajectoryIterator = Collections.emptyIterator();
301
302
303 private Trajectory<G> currentTrajectory;
304
305
306 private int currentTrajectorySize = 0;
307
308
309 private int trajectoryCounter = 0;
310
311
312 private Iterator<Integer> indexIterator = Collections.emptyIterator();
313
314
315 @Override
316 public boolean hasNext()
317 {
318 while (!this.indexIterator.hasNext())
319 {
320 while (!this.trajectoryIterator.hasNext())
321 {
322 if (!this.laneIterator.hasNext())
323 {
324 return false;
325 }
326 Entry<LaneData<?>, TrajectoryGroup<G>> entry = this.laneIterator.next();
327 this.currentLane = entry.getKey();
328 this.trajectoryIterator = entry.getValue().iterator();
329 }
330 this.currentTrajectory = this.trajectoryIterator.next();
331 this.currentTrajectorySize = this.currentTrajectory.size();
332 this.trajectoryCounter++;
333 this.indexIterator = IntStream.range(0, this.currentTrajectory.size()).iterator();
334 }
335 return true;
336 }
337
338
339 @Override
340 public Row next()
341 {
342 Throw.when(!hasNext(), NoSuchElementException.class, "Sampler data has no next row.");
343 Throw.when(this.currentTrajectory.size() != this.currentTrajectorySize, ConcurrentModificationException.class,
344 "Trajectory modified while iterating.");
345
346 int trajectoryIndex = this.indexIterator.next();
347 try
348 {
349
350 Object[] data = getBaseData(trajectoryIndex);
351 int dataIndex = BASE_COLUMNS.size();
352
353
354 for (int i = 0; i < SamplerData.this.extendedDataTypes.size(); i++)
355 {
356 ExtendedDataType<?, ?, ?, G> extendedDataType = SamplerData.this.extendedDataTypes.get(i);
357 data[dataIndex++] = this.currentTrajectory.contains(extendedDataType)
358 ? this.currentTrajectory.getExtendedData(extendedDataType, trajectoryIndex) : null;
359 }
360
361
362 for (int i = 0; i < SamplerData.this.filterDataTypes.size(); i++)
363 {
364 FilterDataType<?> filterDataType = SamplerData.this.filterDataTypes.get(i);
365
366 data[dataIndex++] = trajectoryIndex == 0 && this.currentTrajectory.contains(filterDataType)
367 ? this.currentTrajectory.getFilterData(filterDataType) : null;
368 }
369
370 return new Row(SamplerData.this, data);
371 }
372 catch (SamplingException se)
373 {
374 throw new RuntimeException("Sampling exception during iteration over sampler data.", se);
375 }
376 }
377
378
379
380
381
382
383
384 private Object[] getBaseData(final int trajectoryIndex) throws SamplingException
385 {
386 Object[] data = new Object[SamplerData.this.getNumberOfColumns()];
387 int dataIndex = 0;
388 for (Column<?> column : BASE_COLUMNS)
389 {
390 switch (column.getId())
391 {
392 case "traj#":
393 data[dataIndex] = this.trajectoryCounter;
394 break;
395 case "linkId":
396 data[dataIndex] = this.currentLane.getLinkData().getId();
397 break;
398 case "laneId":
399 data[dataIndex] = this.currentLane.getId();
400 break;
401 case "gtuId":
402 data[dataIndex] = this.currentTrajectory.getGtuId();
403 break;
404 case "t":
405 data[dataIndex] = FloatDuration.instantiateSI(this.currentTrajectory.getT(trajectoryIndex));
406 break;
407 case "x":
408 data[dataIndex] = FloatLength.instantiateSI(this.currentTrajectory.getX(trajectoryIndex));
409 break;
410 case "v":
411 data[dataIndex] = FloatSpeed.instantiateSI(this.currentTrajectory.getV(trajectoryIndex));
412 break;
413 case "a":
414 data[dataIndex] = FloatAcceleration.instantiateSI(this.currentTrajectory.getA(trajectoryIndex));
415 break;
416 default:
417
418 }
419 dataIndex++;
420 }
421 return data;
422 }
423 }
424
425
426
427
428
429
430
431
432
433
434
435
436 public enum Compression
437 {
438
439 NONE,
440
441
442 ZIP
443 }
444
445 }