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(this.x, 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(this.v, 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(this.a, 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(this.t, 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 int from = 0;
435 double fFrom = 0;
436 while (startPosition.si > this.x[from + 1] && from < this.size - 1)
437 {
438 from++;
439 }
440 if (this.x[from] < startPosition.si)
441 {
442 fFrom = (startPosition.si - this.x[from]) / (this.x[from + 1] - this.x[from]);
443 }
444 int to = this.size - 1;
445 double fTo = 0;
446 while (endPosition.si < this.x[to] && to > 0)
447 {
448 to--;
449 }
450 if (to < this.size - 1)
451 {
452 fTo = (endPosition.si - this.x[to]) / (this.x[to + 1] - this.x[to]);
453 }
454 return new Boundaries(from, fFrom, to, fTo);
455 }
456
457
458
459
460
461
462
463 private Boundaries timeBoundaries(final Time startTime, final Time endTime)
464 {
465 int from = 0;
466 double fFrom = 0;
467 while (startTime.si > this.t[from + 1] && from < this.size - 1)
468 {
469 from++;
470 }
471 if (this.t[from] < startTime.si)
472 {
473 fFrom = (startTime.si - this.t[from]) / (this.t[from + 1] - this.t[from]);
474 }
475 int to = this.size - 1;
476 double fTo = 0;
477 while (endTime.si < this.t[to] && to > 0)
478 {
479 to--;
480 }
481 if (to < this.size - 1)
482 {
483 fTo = (endTime.si - this.t[to]) / (this.t[to + 1] - this.t[to]);
484 }
485 return new Boundaries(from, fFrom, to, fTo);
486 }
487
488
489
490
491
492
493
494 @SuppressWarnings("unchecked")
495 private <T> Trajectory subSet(final Boundaries bounds)
496 {
497 Trajectory out = new Trajectory(this.gtuId, this.metaData, this.extendedData.keySet(), this.kpiLaneDirection);
498 if (bounds.from < bounds.to)
499 {
500 int nBefore = bounds.fFrom < 1.0 ? 1 : 0;
501 int nAfter = bounds.fTo > 0.0 ? 1 : 0;
502 int n = bounds.to - bounds.from + nBefore + nAfter;
503 out.x = new float[n];
504 out.v = new float[n];
505 out.a = new float[n];
506 out.t = new float[n];
507 System.arraycopy(this.x, bounds.from + 1, out.x, nBefore, bounds.to - bounds.from);
508 System.arraycopy(this.v, bounds.from + 1, out.v, nBefore, bounds.to - bounds.from);
509 System.arraycopy(this.a, bounds.from + 1, out.a, nBefore, bounds.to - bounds.from);
510 System.arraycopy(this.t, bounds.from + 1, out.t, nBefore, bounds.to - bounds.from);
511 if (nBefore == 1)
512 {
513 out.x[0] = (float) (this.x[bounds.from] * (1 - bounds.fFrom) + this.x[bounds.from + 1] * bounds.fFrom);
514 out.v[0] = (float) (this.v[bounds.from] * (1 - bounds.fFrom) + this.v[bounds.from + 1] * bounds.fFrom);
515 out.a[0] = (float) (this.a[bounds.from] * (1 - bounds.fFrom) + this.a[bounds.from + 1] * bounds.fFrom);
516 out.t[0] = (float) (this.t[bounds.from] * (1 - bounds.fFrom) + this.t[bounds.from + 1] * bounds.fFrom);
517 }
518 if (nAfter == 1)
519 {
520 out.x[n - 1] = (float) (this.x[bounds.to] * (1 - bounds.fTo) + this.x[bounds.to + 1] * bounds.fTo);
521 out.v[n - 1] = (float) (this.v[bounds.to] * (1 - bounds.fTo) + this.v[bounds.to + 1] * bounds.fTo);
522 out.a[n - 1] = (float) (this.a[bounds.to] * (1 - bounds.fTo) + this.a[bounds.to + 1] * bounds.fTo);
523 out.t[n - 1] = (float) (this.t[bounds.to] * (1 - bounds.fTo) + this.t[bounds.to + 1] * bounds.fTo);
524 }
525 out.size = n;
526 for (ExtendedDataType<?> extendedDataType : this.extendedData.keySet())
527 {
528 List<Object> fromList = this.extendedData.get(extendedDataType);
529 List<Object> toList = new ArrayList<>();
530 if (nBefore == 1)
531 {
532 toList.add(((ExtendedDataType<T>) extendedDataType).interpolate((T) fromList.get(bounds.from),
533 (T) fromList.get(bounds.from + 1), bounds.fFrom));
534 }
535 for (int i = bounds.from + 1; i < bounds.to; i++)
536 {
537 toList.add(fromList.get(i));
538 }
539 if (nAfter == 1)
540 {
541 toList.add(((ExtendedDataType<T>) extendedDataType).interpolate((T) fromList.get(bounds.to),
542 (T) fromList.get(bounds.to + 1), bounds.fTo));
543 }
544 out.extendedData.put(extendedDataType, toList);
545 }
546 }
547 return out;
548 }
549
550
551 @Override
552 public int hashCode()
553 {
554 final int prime = 31;
555 int result = 1;
556 result = prime * result + Arrays.hashCode(this.a);
557 result = prime * result + ((this.extendedData == null) ? 0 : this.extendedData.hashCode());
558 result = prime * result + ((this.gtuId == null) ? 0 : this.gtuId.hashCode());
559 result = prime * result + ((this.metaData == null) ? 0 : this.metaData.hashCode());
560 result = prime * result + this.size;
561 result = prime * result + Arrays.hashCode(this.t);
562 result = prime * result + Arrays.hashCode(this.v);
563 result = prime * result + Arrays.hashCode(this.x);
564 return result;
565 }
566
567
568 @Override
569 public boolean equals(final Object obj)
570 {
571 if (this == obj)
572 {
573 return true;
574 }
575 if (obj == null)
576 {
577 return false;
578 }
579 if (getClass() != obj.getClass())
580 {
581 return false;
582 }
583 Trajectory other = (Trajectory) obj;
584 if (!Arrays.equals(this.a, other.a))
585 {
586 return false;
587 }
588 if (this.extendedData == null)
589 {
590 if (other.extendedData != null)
591 {
592 return false;
593 }
594 }
595 else if (!this.extendedData.equals(other.extendedData))
596 {
597 return false;
598 }
599 if (this.gtuId == null)
600 {
601 if (other.gtuId != null)
602 {
603 return false;
604 }
605 }
606 else if (!this.gtuId.equals(other.gtuId))
607 {
608 return false;
609 }
610 if (this.metaData == null)
611 {
612 if (other.metaData != null)
613 {
614 return false;
615 }
616 }
617 else if (!this.metaData.equals(other.metaData))
618 {
619 return false;
620 }
621 if (this.size != other.size)
622 {
623 return false;
624 }
625 if (!Arrays.equals(this.t, other.t))
626 {
627 return false;
628 }
629 if (!Arrays.equals(this.v, other.v))
630 {
631 return false;
632 }
633 if (!Arrays.equals(this.x, other.x))
634 {
635 return false;
636 }
637 return true;
638 }
639
640
641 @Override
642 public String toString()
643 {
644 if (this.size > 0)
645 {
646 return "Trajectory [size=" + this.size + ", x={" + this.x[0] + "..." + this.x[this.size - 1] + "}, t={" + this.t[0]
647 + "..." + this.t[this.size - 1] + "}, metaData=" + this.metaData + ", gtuId=" + this.gtuId + "]";
648 }
649 return "Trajectory [size=" + this.size + ", x={}, t={}, metaData=" + this.metaData + ", gtuId=" + this.gtuId + "]";
650 }
651
652
653
654
655
656
657
658
659
660
661
662
663 private class Boundaries
664 {
665
666 @SuppressWarnings("checkstyle:visibilitymodifier")
667 public final int from;
668
669
670 @SuppressWarnings("checkstyle:visibilitymodifier")
671 public final double fFrom;
672
673
674 @SuppressWarnings("checkstyle:visibilitymodifier")
675 public final int to;
676
677
678 @SuppressWarnings("checkstyle:visibilitymodifier")
679 public final double fTo;
680
681
682
683
684
685
686
687 Boundaries(final int from, final double fFrom, final int to, final double fTo)
688 {
689 Throw.when(from < 0 || from > Trajectory.this.size() - 1, IllegalArgumentException.class,
690 "Argument from (%d) is out of bounds.", from);
691 Throw.when(fFrom < 0 || fFrom > 1, IllegalArgumentException.class, "Argument fFrom (%f) is out of bounds.", fFrom);
692 Throw.when(from == Trajectory.this.size() && fFrom > 0, IllegalArgumentException.class,
693 "Arguments from (%d) and fFrom (%f) are out of bounds.", from, fFrom);
694 Throw.when(to < 0 || to >= Trajectory.this.size(), IllegalArgumentException.class,
695 "Argument to (%d) is out of bounds.", to);
696 Throw.when(fTo < 0 || fTo > 1, IllegalArgumentException.class, "Argument fTo (%f) is out of bounds.", fTo);
697 Throw.when(to == Trajectory.this.size() && fTo > 0, IllegalArgumentException.class,
698 "Arguments to (%d) and fTo (%f) are out of bounds.", to, fTo);
699 this.from = from;
700 this.fFrom = fFrom;
701 this.to = to;
702 this.fTo = fTo;
703 }
704
705
706
707
708
709 public Boundaries intersect(final Boundaries boundaries)
710 {
711 int newFrom;
712 double newFFrom;
713 if (this.from > boundaries.from || this.from == boundaries.from && this.fFrom > boundaries.fFrom)
714 {
715 newFrom = this.from;
716 newFFrom = this.fFrom;
717 }
718 else
719 {
720 newFrom = boundaries.from;
721 newFFrom = boundaries.fFrom;
722 }
723 int newTo;
724 double newFTo;
725 if (this.to < boundaries.to || this.to == boundaries.to && this.fTo < boundaries.fTo)
726 {
727 newTo = this.to;
728 newFTo = this.fTo;
729 }
730 else
731 {
732 newTo = boundaries.to;
733 newFTo = boundaries.fTo;
734 }
735 return new Boundaries(newFrom, newFFrom, newTo, newFTo);
736 }
737
738 }
739
740 }