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