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