1 package org.opentrafficsim.kpi.sampling;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Set;
9
10 import org.djunits.unit.AccelerationUnit;
11 import org.djunits.unit.LengthUnit;
12 import org.djunits.unit.SpeedUnit;
13 import org.djunits.unit.TimeUnit;
14 import org.djunits.value.StorageType;
15 import org.djunits.value.ValueException;
16 import org.djunits.value.vdouble.scalar.Acceleration;
17 import org.djunits.value.vdouble.scalar.Duration;
18 import org.djunits.value.vdouble.scalar.Length;
19 import org.djunits.value.vdouble.scalar.Speed;
20 import org.djunits.value.vdouble.scalar.Time;
21 import org.djunits.value.vfloat.vector.FloatAccelerationVector;
22 import org.djunits.value.vfloat.vector.FloatLengthVector;
23 import org.djunits.value.vfloat.vector.FloatSpeedVector;
24 import org.djunits.value.vfloat.vector.FloatTimeVector;
25 import org.opentrafficsim.kpi.interfaces.GtuDataInterface;
26 import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
27 import org.opentrafficsim.kpi.sampling.meta.MetaData;
28 import org.opentrafficsim.kpi.sampling.meta.MetaDataType;
29
30 import nl.tudelft.simulation.language.Throw;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 public final class Trajectory
48 {
49
50
51 private static final int DEFAULT_CAPACITY = 10;
52
53
54 private int size = 0;
55
56
57
58
59
60 private float[] x = new float[DEFAULT_CAPACITY];
61
62
63 private float[] v = new float[DEFAULT_CAPACITY];
64
65
66 private float[] a = new float[DEFAULT_CAPACITY];
67
68
69 private float[] t = new float[DEFAULT_CAPACITY];
70
71
72 private final String gtuId;
73
74
75 private final MetaData metaData;
76
77
78 private final Map<ExtendedDataType<?>, List<Object>> extendedData = new HashMap<>();
79
80
81 private final KpiLaneDirection kpiLaneDirection;
82
83
84
85
86
87
88
89 public Trajectory(final GtuDataInterface gtu, final MetaData metaData, final Set<ExtendedDataType<?>> extendedData,
90 final KpiLaneDirection kpiLaneDirection)
91 {
92 this(gtu == null ? null : gtu.getId(), metaData, extendedData, kpiLaneDirection);
93 }
94
95
96
97
98
99
100
101
102 private Trajectory(final String gtuId, final MetaData metaData, final Set<ExtendedDataType<?>> extendedData,
103 final KpiLaneDirection kpiLaneDirection)
104 {
105 Throw.whenNull(gtuId, "GTU may not be null.");
106 Throw.whenNull(metaData, "Meta data may not be null.");
107 Throw.whenNull(extendedData, "Extended data may not be null.");
108 Throw.whenNull(kpiLaneDirection, "Lane direction may not be null.");
109 this.gtuId = gtuId;
110 this.metaData = new MetaData(metaData);
111 for (ExtendedDataType<?> dataType : extendedData)
112 {
113 this.extendedData.put(dataType, new ArrayList<>());
114 }
115 this.kpiLaneDirection = kpiLaneDirection;
116 }
117
118
119
120
121
122
123
124
125
126 public void add(final Length position, final Speed speed, final Acceleration acceleration, final Time time)
127 {
128 add(position, speed, acceleration, time, null);
129 }
130
131
132
133
134
135
136
137
138
139
140 public void add(final Length position, final Speed speed, final Acceleration acceleration, final Time time,
141 final GtuDataInterface gtu)
142 {
143 Throw.whenNull(position, "Position may not be null.");
144 Throw.whenNull(speed, "Speed may not be null.");
145 Throw.whenNull(acceleration, "Acceleration may not be null.");
146 Throw.whenNull(time, "Time may not be null.");
147 Throw.whenNull(gtu, "GTU may not be null.");
148 if (this.size == this.x.length)
149 {
150 int cap = this.size + (this.size >> 1);
151 this.x = Arrays.copyOf(this.x, cap);
152 this.v = Arrays.copyOf(this.v, cap);
153 this.a = Arrays.copyOf(this.a, cap);
154 this.t = Arrays.copyOf(this.t, cap);
155 }
156 this.x[this.size] = (float) this.kpiLaneDirection.getPositionInDirection(position).si;
157 this.v[this.size] = (float) speed.si;
158 this.a[this.size] = (float) acceleration.si;
159 this.t[this.size] = (float) time.si;
160 this.size++;
161 if (gtu != null)
162 {
163 for (ExtendedDataType<?> extendedDataType : this.extendedData.keySet())
164 {
165 this.extendedData.get(extendedDataType).add(this.size - 1, extendedDataType.getValue(gtu));
166 }
167 }
168 else
169 {
170 for (ExtendedDataType<?> extendedDataType : this.extendedData.keySet())
171 {
172 this.extendedData.get(extendedDataType).add(this.size - 1, null);
173 }
174 }
175 }
176
177
178
179
180 public int size()
181 {
182 return this.size;
183 }
184
185
186
187
188 public String getGtuId()
189 {
190 return this.gtuId;
191 }
192
193
194
195
196
197 public float[] getX()
198 {
199 return Arrays.copyOf(this.x, this.size);
200 }
201
202
203
204
205 public float[] getV()
206 {
207 return Arrays.copyOf(this.v, this.size);
208 }
209
210
211
212
213 public float[] getA()
214 {
215 return Arrays.copyOf(this.a, this.size);
216 }
217
218
219
220
221 public float[] getT()
222 {
223 return Arrays.copyOf(this.t, this.size);
224 }
225
226
227
228
229
230 public FloatLengthVector getPosition()
231 {
232 try
233 {
234 return new FloatLengthVector(getX(), LengthUnit.SI, StorageType.DENSE);
235 }
236 catch (ValueException exception)
237 {
238
239 throw new RuntimeException("Could not return trajectory data.", exception);
240 }
241 }
242
243
244
245
246 public FloatSpeedVector getSpeed()
247 {
248 try
249 {
250 return new FloatSpeedVector(getV(), SpeedUnit.SI, StorageType.DENSE);
251 }
252 catch (ValueException exception)
253 {
254
255 throw new RuntimeException("Could not return trajectory data.", exception);
256 }
257 }
258
259
260
261
262 public FloatAccelerationVector getAcceleration()
263 {
264 try
265 {
266 return new FloatAccelerationVector(getA(), AccelerationUnit.SI, StorageType.DENSE);
267 }
268 catch (ValueException exception)
269 {
270
271 throw new RuntimeException("Could not return trajectory data.", exception);
272 }
273 }
274
275
276
277
278 public FloatTimeVector getTime()
279 {
280 try
281 {
282 return new FloatTimeVector(getT(), TimeUnit.SI, StorageType.DENSE);
283 }
284 catch (ValueException exception)
285 {
286
287 throw new RuntimeException("Could not return trajectory data.", exception);
288 }
289 }
290
291
292
293
294
295 public Length getTotalLength()
296 {
297
298
299 if (this.size == 0)
300 {
301 return Length.ZERO;
302 }
303 return new Length(this.x[this.size - 1] - this.x[0], LengthUnit.SI);
304 }
305
306
307
308
309
310 public Duration getTotalDuration()
311 {
312
313
314 if (this.size == 0)
315 {
316 return Duration.ZERO;
317 }
318 return new Duration(this.t[this.size - 1] - this.t[0], TimeUnit.SI);
319 }
320
321
322
323
324
325 public boolean contains(final MetaDataType<?> metaDataType)
326 {
327 return this.metaData.contains(metaDataType);
328 }
329
330
331
332
333
334
335 public <T> T getMetaData(final MetaDataType<T> metaDataType)
336 {
337 return this.metaData.get(metaDataType);
338 }
339
340
341
342
343
344 public boolean contains(final ExtendedDataType<?> extendedDataType)
345 {
346 return this.extendedData.containsKey(extendedDataType);
347 }
348
349
350
351
352
353
354
355 @SuppressWarnings("unchecked")
356 public <T> List<T> getExtendedData(final ExtendedDataType<T> extendedDataType) throws SamplingException
357 {
358 Throw.when(!this.extendedData.containsKey(extendedDataType), SamplingException.class,
359 "Extended data type %s is not in the trajectory.", extendedDataType);
360 return (List<T>) this.extendedData.get(extendedDataType);
361 }
362
363
364
365
366
367
368
369
370
371
372 public Trajectory subSet(final Length startPosition, final Length endPosition)
373 {
374 Throw.whenNull(startPosition, "Start position may not be null");
375 Throw.whenNull(endPosition, "End position may not be null");
376 Length length0 = this.kpiLaneDirection.getPositionInDirection(startPosition);
377 Length length1 = this.kpiLaneDirection.getPositionInDirection(endPosition);
378 Throw.when(length0.gt(length1), IllegalArgumentException.class,
379 "Start position should be smaller than end position in the direction of travel");
380 return subSet(spaceBoundaries(length0, length1));
381 }
382
383
384
385
386
387
388
389
390
391
392 public Trajectory subSet(final Time startTime, final Time endTime)
393 {
394 Throw.whenNull(startTime, "Start time may not be null");
395 Throw.whenNull(endTime, "End time may not be null");
396 Throw.when(startTime.gt(endTime), IllegalArgumentException.class, "Start time should be smaller than end time.");
397 return subSet(timeBoundaries(startTime, endTime));
398 }
399
400
401
402
403
404
405
406
407
408
409
410
411 public Trajectory subSet(final Length startPosition, final Length endPosition, final Time startTime, final Time endTime)
412 {
413
414 Throw.whenNull(startPosition, "Start position may not be null");
415 Throw.whenNull(endPosition, "End position may not be null");
416 Length length0 = this.kpiLaneDirection.getPositionInDirection(startPosition);
417 Length length1 = this.kpiLaneDirection.getPositionInDirection(endPosition);
418 Throw.when(length0.gt(length1), IllegalArgumentException.class,
419 "Start position should be smaller than end position in the direction of travel");
420 Throw.whenNull(startTime, "Start time may not be null");
421 Throw.whenNull(endTime, "End time may not be null");
422 Throw.when(startTime.gt(endTime), IllegalArgumentException.class, "Start time should be smaller than end time.");
423 return subSet(spaceBoundaries(length0, length1).intersect(timeBoundaries(startTime, endTime)));
424 }
425
426
427
428
429
430
431
432 private Boundaries spaceBoundaries(final Length startPosition, final Length endPosition)
433 {
434 if (startPosition.si > this.x[this.size - 1] || endPosition.si < this.x[0])
435 {
436 return new Boundaries(0, 0.0, 0, 0.0);
437 }
438 int from = 0;
439 double fFrom = 0;
440
441 float startPos = (float) startPosition.si;
442 float endPos = (float) endPosition.si;
443 while (startPos > this.x[from + 1] && from < this.size - 1)
444 {
445 from++;
446 }
447 if (this.x[from] < startPos)
448 {
449 fFrom = (startPos - this.x[from]) / (this.x[from + 1] - this.x[from]);
450 }
451 int to = this.size - 1;
452 double fTo = 0;
453 while (endPos < this.x[to] && to > 0)
454 {
455 to--;
456 }
457 if (to < this.size - 1)
458 {
459 fTo = (endPos - this.x[to]) / (this.x[to + 1] - this.x[to]);
460 }
461 return new Boundaries(from, fFrom, to, fTo);
462 }
463
464
465
466
467
468
469
470 private Boundaries timeBoundaries(final Time startTime, final Time endTime)
471 {
472 if (startTime.si > this.t[this.size - 1] || endTime.si < this.t[0])
473 {
474 return new Boundaries(0, 0.0, 0, 0.0);
475 }
476 int from = 0;
477 double fFrom = 0;
478
479 float startTim = (float) startTime.si;
480 float endTim = (float) endTime.si;
481 while (startTim > this.t[from + 1] && from < this.size - 1)
482 {
483 from++;
484 }
485 if (this.t[from] < startTim)
486 {
487 fFrom = (startTim - this.t[from]) / (this.t[from + 1] - this.t[from]);
488 }
489 int to = this.size - 1;
490 double fTo = 0;
491 while (endTim < this.t[to] && to > 0)
492 {
493 to--;
494 }
495 if (to < this.size - 1)
496 {
497 fTo = (endTim - this.t[to]) / (this.t[to + 1] - this.t[to]);
498 }
499 return new Boundaries(from, fFrom, to, fTo);
500 }
501
502
503
504
505
506
507
508 @SuppressWarnings("unchecked")
509 private <T> Trajectory subSet(final Boundaries bounds)
510 {
511 Trajectory out = new Trajectory(this.gtuId, this.metaData, this.extendedData.keySet(), this.kpiLaneDirection);
512 if (bounds.from < bounds.to)
513 {
514 int nBefore = bounds.fFrom < 1.0 ? 1 : 0;
515 int nAfter = bounds.fTo > 0.0 ? 1 : 0;
516 int n = bounds.to - bounds.from + nBefore + nAfter;
517 out.x = new float[n];
518 out.v = new float[n];
519 out.a = new float[n];
520 out.t = new float[n];
521 System.arraycopy(this.x, bounds.from + 1, out.x, nBefore, bounds.to - bounds.from);
522 System.arraycopy(this.v, bounds.from + 1, out.v, nBefore, bounds.to - bounds.from);
523 System.arraycopy(this.a, bounds.from + 1, out.a, nBefore, bounds.to - bounds.from);
524 System.arraycopy(this.t, bounds.from + 1, out.t, nBefore, bounds.to - bounds.from);
525 if (nBefore == 1)
526 {
527 out.x[0] = (float) (this.x[bounds.from] * (1 - bounds.fFrom) + this.x[bounds.from + 1] * bounds.fFrom);
528 out.v[0] = (float) (this.v[bounds.from] * (1 - bounds.fFrom) + this.v[bounds.from + 1] * bounds.fFrom);
529 out.a[0] = (float) (this.a[bounds.from] * (1 - bounds.fFrom) + this.a[bounds.from + 1] * bounds.fFrom);
530 out.t[0] = (float) (this.t[bounds.from] * (1 - bounds.fFrom) + this.t[bounds.from + 1] * bounds.fFrom);
531 }
532 if (nAfter == 1)
533 {
534 out.x[n - 1] = (float) (this.x[bounds.to] * (1 - bounds.fTo) + this.x[bounds.to + 1] * bounds.fTo);
535 out.v[n - 1] = (float) (this.v[bounds.to] * (1 - bounds.fTo) + this.v[bounds.to + 1] * bounds.fTo);
536 out.a[n - 1] = (float) (this.a[bounds.to] * (1 - bounds.fTo) + this.a[bounds.to + 1] * bounds.fTo);
537 out.t[n - 1] = (float) (this.t[bounds.to] * (1 - bounds.fTo) + this.t[bounds.to + 1] * bounds.fTo);
538 }
539 out.size = n;
540 for (ExtendedDataType<?> extendedDataType : this.extendedData.keySet())
541 {
542 List<Object> fromList = this.extendedData.get(extendedDataType);
543 List<Object> toList = new ArrayList<>();
544 if (nBefore == 1)
545 {
546 toList.add(((ExtendedDataType<T>) extendedDataType).interpolate((T) fromList.get(bounds.from),
547 (T) fromList.get(bounds.from + 1), bounds.fFrom));
548 }
549 for (int i = bounds.from + 1; i < bounds.to; i++)
550 {
551 toList.add(fromList.get(i));
552 }
553 if (nAfter == 1)
554 {
555 toList.add(((ExtendedDataType<T>) extendedDataType).interpolate((T) fromList.get(bounds.to),
556 (T) fromList.get(bounds.to + 1), bounds.fTo));
557 }
558 out.extendedData.put(extendedDataType, toList);
559 }
560 }
561 return out;
562 }
563
564
565 @Override
566 public int hashCode()
567 {
568 final int prime = 31;
569 int result = 1;
570 result = prime * result + ((this.gtuId == null) ? 0 : this.gtuId.hashCode());
571 result = prime * result + this.size;
572 if (this.size > 0)
573 {
574 result = prime * result + Float.floatToIntBits(this.t[0]);
575 }
576 return result;
577 }
578
579
580 @Override
581 public boolean equals(final Object obj)
582 {
583 if (this == obj)
584 {
585 return true;
586 }
587 if (obj == null)
588 {
589 return false;
590 }
591 if (getClass() != obj.getClass())
592 {
593 return false;
594 }
595 Trajectory other = (Trajectory) obj;
596 if (this.size != other.size)
597 {
598 return false;
599 }
600 if (this.gtuId == null)
601 {
602 if (other.gtuId != null)
603 {
604 return false;
605 }
606 }
607 else if (!this.gtuId.equals(other.gtuId))
608 {
609 return false;
610 }
611 if (this.size > 0)
612 {
613 if (this.t[0] != other.t[0])
614 {
615 return false;
616 }
617 }
618 return true;
619 }
620
621
622 @Override
623 public String toString()
624 {
625 if (this.size > 0)
626 {
627 return "Trajectory [size=" + this.size + ", x={" + this.x[0] + "..." + this.x[this.size - 1] + "}, t={" + this.t[0]
628 + "..." + this.t[this.size - 1] + "}, metaData=" + this.metaData + ", gtuId=" + this.gtuId + "]";
629 }
630 return "Trajectory [size=" + this.size + ", x={}, t={}, metaData=" + this.metaData + ", gtuId=" + this.gtuId + "]";
631 }
632
633
634
635
636
637
638
639
640
641
642
643
644 private class Boundaries
645 {
646
647 @SuppressWarnings("checkstyle:visibilitymodifier")
648 public final int from;
649
650
651 @SuppressWarnings("checkstyle:visibilitymodifier")
652 public final double fFrom;
653
654
655 @SuppressWarnings("checkstyle:visibilitymodifier")
656 public final int to;
657
658
659 @SuppressWarnings("checkstyle:visibilitymodifier")
660 public final double fTo;
661
662
663
664
665
666
667
668 Boundaries(final int from, final double fFrom, final int to, final double fTo)
669 {
670 Throw.when(from < 0 || from > Trajectory.this.size() - 1, IllegalArgumentException.class,
671 "Argument from (%d) is out of bounds.", from);
672 Throw.when(fFrom < 0 || fFrom > 1, IllegalArgumentException.class, "Argument fFrom (%f) is out of bounds.", fFrom);
673 Throw.when(from == Trajectory.this.size() && fFrom > 0, IllegalArgumentException.class,
674 "Arguments from (%d) and fFrom (%f) are out of bounds.", from, fFrom);
675 Throw.when(to < 0 || to >= Trajectory.this.size(), IllegalArgumentException.class,
676 "Argument to (%d) is out of bounds.", to);
677 Throw.when(fTo < 0 || fTo > 1, IllegalArgumentException.class, "Argument fTo (%f) is out of bounds.", fTo);
678 Throw.when(to == Trajectory.this.size() && fTo > 0, IllegalArgumentException.class,
679 "Arguments to (%d) and fTo (%f) are out of bounds.", to, fTo);
680 this.from = from;
681 this.fFrom = fFrom;
682 this.to = to;
683 this.fTo = fTo;
684 }
685
686
687
688
689
690 public Boundaries intersect(final Boundaries boundaries)
691 {
692 int newFrom;
693 double newFFrom;
694 if (this.from > boundaries.from || this.from == boundaries.from && this.fFrom > boundaries.fFrom)
695 {
696 newFrom = this.from;
697 newFFrom = this.fFrom;
698 }
699 else
700 {
701 newFrom = boundaries.from;
702 newFFrom = boundaries.fFrom;
703 }
704 int newTo;
705 double newFTo;
706 if (this.to < boundaries.to || this.to == boundaries.to && this.fTo < boundaries.fTo)
707 {
708 newTo = this.to;
709 newFTo = this.fTo;
710 }
711 else
712 {
713 newTo = boundaries.to;
714 newFTo = boundaries.fTo;
715 }
716 return new Boundaries(newFrom, newFFrom, newTo, newFTo);
717 }
718
719 }
720
721 }