1 package org.opentrafficsim.kpi.sampling;
2
3 import java.util.Arrays;
4 import java.util.LinkedHashMap;
5 import java.util.Map;
6 import java.util.Set;
7
8 import org.djunits.unit.AccelerationUnit;
9 import org.djunits.unit.DurationUnit;
10 import org.djunits.unit.LengthUnit;
11 import org.djunits.unit.SpeedUnit;
12 import org.djunits.unit.TimeUnit;
13 import org.djunits.value.StorageType;
14 import org.djunits.value.ValueException;
15 import org.djunits.value.vdouble.scalar.Acceleration;
16 import org.djunits.value.vdouble.scalar.Duration;
17 import org.djunits.value.vdouble.scalar.Length;
18 import org.djunits.value.vdouble.scalar.Speed;
19 import org.djunits.value.vdouble.scalar.Time;
20 import org.djunits.value.vfloat.vector.FloatAccelerationVector;
21 import org.djunits.value.vfloat.vector.FloatLengthVector;
22 import org.djunits.value.vfloat.vector.FloatSpeedVector;
23 import org.djunits.value.vfloat.vector.FloatTimeVector;
24 import org.djutils.exceptions.Throw;
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 public final class Trajectory<G extends GtuDataInterface>
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<?, ?, ?, G>, Object> extendedData = new LinkedHashMap<>();
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<?, ?, ?, G>> 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<?, ?, ?, G>> 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<?, ?, ?, G> dataType : extendedData)
112 {
113 this.extendedData.put(dataType, dataType.initializeStorage());
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, final G 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<?, ?, ?, G> 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<T, ?, S, G> extendedDataType, final G gtu)
172 {
173 S in = (S) this.extendedData.get(extendedDataType);
174 S out = extendedDataType.setValue(in, this.size, extendedDataType.getValue(gtu));
175 if (in != out)
176 {
177 this.extendedData.put(extendedDataType, out);
178 }
179 }
180
181
182
183
184 public int size()
185 {
186 return this.size;
187 }
188
189
190
191
192 public String getGtuId()
193 {
194 return this.gtuId;
195 }
196
197
198
199
200
201 public float[] getX()
202 {
203 return Arrays.copyOf(this.x, this.size);
204 }
205
206
207
208
209 public float[] getV()
210 {
211 return Arrays.copyOf(this.v, this.size);
212 }
213
214
215
216
217 public float[] getA()
218 {
219 return Arrays.copyOf(this.a, this.size);
220 }
221
222
223
224
225 public float[] getT()
226 {
227 return Arrays.copyOf(this.t, this.size);
228 }
229
230
231
232
233
234
235 public int binarySearchX(final float position)
236 {
237 if (this.x[0] >= position)
238 {
239 return 0;
240 }
241 int index = Arrays.binarySearch(this.x, 0, this.size, position);
242 return index < 0 ? -index - 2 : index;
243 }
244
245
246
247
248
249
250 public int binarySearchT(final float time)
251 {
252 if (this.t[0] >= time)
253 {
254 return 0;
255 }
256 int index = Arrays.binarySearch(this.t, 0, this.size, time);
257 return index < 0 ? -index - 2 : index;
258 }
259
260
261
262
263
264
265
266 public float getX(final int index) throws SamplingException
267 {
268 checkSample(index);
269 return this.x[index];
270 }
271
272
273
274
275
276
277
278 public float getV(final int index) throws SamplingException
279 {
280 checkSample(index);
281 return this.v[index];
282 }
283
284
285
286
287
288
289
290 public float getA(final int index) throws SamplingException
291 {
292 checkSample(index);
293 return this.a[index];
294 }
295
296
297
298
299
300
301
302 public float getT(final int index) throws SamplingException
303 {
304 checkSample(index);
305 return this.t[index];
306 }
307
308
309
310
311
312
313
314
315
316
317 @SuppressWarnings("unchecked")
318 public <T, S> T getExtendedData(final ExtendedDataType<T, ?, S, ?> extendedDataType, final int index)
319 throws SamplingException
320 {
321 checkSample(index);
322 return extendedDataType.getStorageValue((S) this.extendedData.get(extendedDataType), index);
323 }
324
325
326
327
328
329
330 private void checkSample(final int index) throws SamplingException
331 {
332 Throw.when(index < 0 || index >= this.size, SamplingException.class, "Index is out of bounds.");
333 }
334
335
336
337
338
339 public FloatLengthVector getPosition()
340 {
341 try
342 {
343 return new FloatLengthVector(getX(), LengthUnit.SI, StorageType.DENSE);
344 }
345 catch (ValueException exception)
346 {
347
348 throw new RuntimeException("Could not return trajectory data.", exception);
349 }
350 }
351
352
353
354
355 public FloatSpeedVector getSpeed()
356 {
357 try
358 {
359 return new FloatSpeedVector(getV(), SpeedUnit.SI, StorageType.DENSE);
360 }
361 catch (ValueException exception)
362 {
363
364 throw new RuntimeException("Could not return trajectory data.", exception);
365 }
366 }
367
368
369
370
371 public FloatAccelerationVector getAcceleration()
372 {
373 try
374 {
375 return new FloatAccelerationVector(getA(), AccelerationUnit.SI, StorageType.DENSE);
376 }
377 catch (ValueException exception)
378 {
379
380 throw new RuntimeException("Could not return trajectory data.", exception);
381 }
382 }
383
384
385
386
387 public FloatTimeVector getTime()
388 {
389 try
390 {
391 return new FloatTimeVector(getT(), TimeUnit.BASE_SECOND, StorageType.DENSE);
392 }
393 catch (ValueException exception)
394 {
395
396 throw new RuntimeException("Could not return trajectory data.", exception);
397 }
398 }
399
400
401
402
403
404 public Length getTotalLength()
405 {
406
407
408 if (this.size == 0)
409 {
410 return Length.ZERO;
411 }
412 return new Length(this.x[this.size - 1] - this.x[0], LengthUnit.SI);
413 }
414
415
416
417
418
419 public Duration getTotalDuration()
420 {
421
422
423 if (this.size == 0)
424 {
425 return Duration.ZERO;
426 }
427 return new Duration(this.t[this.size - 1] - this.t[0], DurationUnit.SI);
428 }
429
430
431
432
433
434 public boolean contains(final MetaDataType<?> metaDataType)
435 {
436 return this.metaData.contains(metaDataType);
437 }
438
439
440
441
442
443
444 public <T> T getMetaData(final MetaDataType<T> metaDataType)
445 {
446 return this.metaData.get(metaDataType);
447 }
448
449
450
451
452
453 public Set<MetaDataType<?>> getMetaDataTypes()
454 {
455 return this.metaData.getMetaDataTypes();
456 }
457
458
459
460
461
462 public boolean contains(final ExtendedDataType<?, ?, ?, ?> extendedDataType)
463 {
464 return this.extendedData.containsKey(extendedDataType);
465 }
466
467
468
469
470
471
472
473
474 @SuppressWarnings("unchecked")
475 public <O, S> O getExtendedData(final ExtendedDataType<?, O, S, ?> extendedDataType) throws SamplingException
476 {
477 Throw.when(!this.extendedData.containsKey(extendedDataType), SamplingException.class,
478 "Extended data type %s is not in the trajectory.", extendedDataType);
479 return extendedDataType.convert((S) this.extendedData.get(extendedDataType), this.size);
480 }
481
482
483
484
485
486 public Set<ExtendedDataType<?, ?, ?, G>> getExtendedDataTypes()
487 {
488 return this.extendedData.keySet();
489 }
490
491
492
493
494
495
496
497
498
499
500 @SuppressWarnings("synthetic-access")
501 public SpaceTimeView getSpaceTimeView(final Length startPosition, final Length endPosition, final Time startTime,
502 final Time endTime)
503 {
504 if (size() == 0)
505 {
506 return new SpaceTimeView(Length.ZERO, Duration.ZERO);
507 }
508 Length length0 = this.kpiLaneDirection.getPositionInDirection(startPosition);
509 Length length1 = this.kpiLaneDirection.getPositionInDirection(endPosition);
510 Boundaries bounds = spaceBoundaries(length0, length1).intersect(timeBoundaries(startTime, endTime));
511 double xFrom;
512 double tFrom;
513 if (bounds.fFrom > 0.0)
514 {
515 xFrom = this.x[bounds.from] * (1 - bounds.fFrom) + this.x[bounds.from + 1] * bounds.fFrom;
516 tFrom = this.t[bounds.from] * (1 - bounds.fFrom) + this.t[bounds.from + 1] * bounds.fFrom;
517 }
518 else
519 {
520 xFrom = this.x[bounds.from];
521 tFrom = this.t[bounds.from];
522 }
523 double xTo;
524 double tTo;
525 if (bounds.fTo > 0.0)
526 {
527 xTo = this.x[bounds.to] * (1 - bounds.fTo) + this.x[bounds.to + 1] * bounds.fTo;
528 tTo = this.t[bounds.to] * (1 - bounds.fTo) + this.t[bounds.to + 1] * bounds.fTo;
529 }
530 else
531 {
532 xTo = this.x[bounds.to];
533 tTo = this.t[bounds.to];
534 }
535 return new SpaceTimeView(Length.createSI(xTo - xFrom), Duration.createSI(tTo - tFrom));
536 }
537
538
539
540
541
542
543
544
545
546
547 public Trajectory<G> subSet(final Length startPosition, final Length endPosition)
548 {
549 Throw.whenNull(startPosition, "Start position may not be null");
550 Throw.whenNull(endPosition, "End position may not be null");
551 Length length0 = this.kpiLaneDirection.getPositionInDirection(startPosition);
552 Length length1 = this.kpiLaneDirection.getPositionInDirection(endPosition);
553 Throw.when(length0.gt(length1), IllegalArgumentException.class,
554 "Start position should be smaller than end position in the direction of travel");
555 if (this.size == 0)
556 {
557 return new Trajectory<>(this.gtuId, this.metaData, this.extendedData.keySet(), this.kpiLaneDirection);
558 }
559 return subSet(spaceBoundaries(length0, length1));
560 }
561
562
563
564
565
566
567
568
569
570 public Trajectory<G> subSet(final Time startTime, final Time endTime)
571 {
572 Throw.whenNull(startTime, "Start time may not be null");
573 Throw.whenNull(endTime, "End time may not be null");
574 Throw.when(startTime.gt(endTime), IllegalArgumentException.class, "Start time should be smaller than end time.");
575 if (this.size == 0)
576 {
577 return new Trajectory<>(this.gtuId, this.metaData, this.extendedData.keySet(), this.kpiLaneDirection);
578 }
579 return subSet(timeBoundaries(startTime, endTime));
580 }
581
582
583
584
585
586
587
588
589
590
591
592 public Trajectory<G> subSet(final Length startPosition, final Length endPosition, final Time startTime, final Time endTime)
593 {
594
595 Throw.whenNull(startPosition, "Start position may not be null");
596 Throw.whenNull(endPosition, "End position may not be null");
597 Length length0 = this.kpiLaneDirection.getPositionInDirection(startPosition);
598 Length length1 = this.kpiLaneDirection.getPositionInDirection(endPosition);
599 Throw.when(length0.gt(length1), IllegalArgumentException.class,
600 "Start position should be smaller than end position in the direction of travel");
601 Throw.whenNull(startTime, "Start time may not be null");
602 Throw.whenNull(endTime, "End time may not be null");
603 Throw.when(startTime.gt(endTime), IllegalArgumentException.class, "Start time should be smaller than end time.");
604 if (this.size == 0)
605 {
606 return new Trajectory<>(this.gtuId, this.metaData, this.extendedData.keySet(), this.kpiLaneDirection);
607 }
608 return subSet(spaceBoundaries(length0, length1).intersect(timeBoundaries(startTime, endTime)));
609 }
610
611
612
613
614
615
616
617 private Boundaries spaceBoundaries(final Length startPosition, final Length endPosition)
618 {
619 if (startPosition.si > this.x[this.size - 1] || endPosition.si < this.x[0])
620 {
621 return new Boundaries(0, 0.0, 0, 0.0);
622 }
623
624 float startPos = (float) startPosition.si;
625 float endPos = (float) endPosition.si;
626 Boundary from = getBoundaryAtPosition(startPos, false);
627 Boundary to = getBoundaryAtPosition(endPos, true);
628 return new Boundaries(from.index, from.fraction, to.index, to.fraction);
629
630
631
632
633
634
635
636
637
638
639
640
641
642 }
643
644
645
646
647
648
649
650 private Boundaries timeBoundaries(final Time startTime, final Time endTime)
651 {
652 if (startTime.si > this.t[this.size - 1] || endTime.si < this.t[0])
653 {
654 return new Boundaries(0, 0.0, 0, 0.0);
655 }
656
657 float startTim = (float) startTime.si;
658 float endTim = (float) endTime.si;
659 Boundary from = getBoundaryAtTime(startTim, false);
660 Boundary to = getBoundaryAtTime(endTim, true);
661 return new Boundaries(from.index, from.fraction, to.index, to.fraction);
662
663
664
665
666
667
668
669
670
671
672
673
674
675 }
676
677
678
679
680
681
682
683 private Boundary getBoundaryAtPosition(final float position, final boolean end)
684 {
685 int index = binarySearchX(position);
686 double fraction = 0;
687 if (end ? index < this.size - 1 : this.x[index] < position)
688 {
689 fraction = (position - this.x[index]) / (this.x[index + 1] - this.x[index]);
690 }
691 return new Boundary(index, fraction);
692 }
693
694
695
696
697
698
699
700 private Boundary getBoundaryAtTime(final float time, final boolean end)
701 {
702 int index = binarySearchT(time);
703 double fraction = 0;
704 if (end ? index < this.size - 1 : this.t[index] < time)
705 {
706 fraction = (time - this.t[index]) / (this.t[index + 1] - this.t[index]);
707 }
708 return new Boundary(index, fraction);
709 }
710
711
712
713
714
715
716 public Time getTimeAtPosition(final Length position)
717 {
718 return Time.createSI(getBoundaryAtPosition((float) position.si, false).getValue(this.t));
719 }
720
721
722
723
724
725
726 public Speed getSpeedAtPosition(final Length position)
727 {
728 return Speed.createSI(getBoundaryAtPosition((float) position.si, false).getValue(this.v));
729 }
730
731
732
733
734
735
736 public Acceleration getAccelerationAtPosition(final Length position)
737 {
738 return Acceleration.createSI(getBoundaryAtPosition((float) position.si, false).getValue(this.a));
739 }
740
741
742
743
744
745
746 public Length getPositionAtTime(final Time time)
747 {
748 return Length.createSI(getBoundaryAtTime((float) time.si, false).getValue(this.x));
749 }
750
751
752
753
754
755
756 public Speed getSpeedAtTime(final Time time)
757 {
758 return Speed.createSI(getBoundaryAtTime((float) time.si, false).getValue(this.v));
759 }
760
761
762
763
764
765
766 public Acceleration getAccelerationAtTime(final Time time)
767 {
768 return Acceleration.createSI(getBoundaryAtTime((float) time.si, false).getValue(this.a));
769 }
770
771
772
773
774
775
776
777
778 @SuppressWarnings("unchecked")
779 private <T, S> Trajectory<G> subSet(final Boundaries bounds)
780 {
781 Trajectory<G> out = new Trajectory<>(this.gtuId, this.metaData, this.extendedData.keySet(), this.kpiLaneDirection);
782 if (bounds.from < bounds.to)
783 {
784 int nBefore = bounds.fFrom < 1.0 ? 1 : 0;
785 int nAfter = bounds.fTo > 0.0 ? 1 : 0;
786 int n = bounds.to - bounds.from + nBefore + nAfter;
787 out.x = new float[n];
788 out.v = new float[n];
789 out.a = new float[n];
790 out.t = new float[n];
791 System.arraycopy(this.x, bounds.from + 1, out.x, nBefore, bounds.to - bounds.from);
792 System.arraycopy(this.v, bounds.from + 1, out.v, nBefore, bounds.to - bounds.from);
793 System.arraycopy(this.a, bounds.from + 1, out.a, nBefore, bounds.to - bounds.from);
794 System.arraycopy(this.t, bounds.from + 1, out.t, nBefore, bounds.to - bounds.from);
795 if (nBefore == 1)
796 {
797 out.x[0] = (float) (this.x[bounds.from] * (1 - bounds.fFrom) + this.x[bounds.from + 1] * bounds.fFrom);
798 out.v[0] = (float) (this.v[bounds.from] * (1 - bounds.fFrom) + this.v[bounds.from + 1] * bounds.fFrom);
799 out.a[0] = (float) (this.a[bounds.from] * (1 - bounds.fFrom) + this.a[bounds.from + 1] * bounds.fFrom);
800 out.t[0] = (float) (this.t[bounds.from] * (1 - bounds.fFrom) + this.t[bounds.from + 1] * bounds.fFrom);
801 }
802 if (nAfter == 1)
803 {
804 out.x[n - 1] = (float) (this.x[bounds.to] * (1 - bounds.fTo) + this.x[bounds.to + 1] * bounds.fTo);
805 out.v[n - 1] = (float) (this.v[bounds.to] * (1 - bounds.fTo) + this.v[bounds.to + 1] * bounds.fTo);
806 out.a[n - 1] = (float) (this.a[bounds.to] * (1 - bounds.fTo) + this.a[bounds.to + 1] * bounds.fTo);
807 out.t[n - 1] = (float) (this.t[bounds.to] * (1 - bounds.fTo) + this.t[bounds.to + 1] * bounds.fTo);
808 }
809 out.size = n;
810 for (ExtendedDataType<?, ?, ?, G> extendedDataType : this.extendedData.keySet())
811 {
812 int j = 0;
813 ExtendedDataType<T, ?, S, G> edt = (ExtendedDataType<T, ?, S, G>) extendedDataType;
814 S fromList = (S) this.extendedData.get(extendedDataType);
815 S toList = edt.initializeStorage();
816 try
817 {
818 if (nBefore == 1)
819 {
820 toList = edt.setValue(toList, j,
821 ((ExtendedDataType<T, ?, ?, G>) extendedDataType).interpolate(
822 edt.getStorageValue(fromList, bounds.from),
823 edt.getStorageValue(fromList, bounds.from + 1), bounds.fFrom));
824 j++;
825 }
826 for (int i = bounds.from + 1; i <= bounds.to; i++)
827 {
828 toList = edt.setValue(toList, j, edt.getStorageValue(fromList, i));
829 j++;
830 }
831 if (nAfter == 1)
832 {
833 toList = edt.setValue(toList, j,
834 ((ExtendedDataType<T, ?, ?, G>) extendedDataType).interpolate(
835 edt.getStorageValue(fromList, bounds.to), edt.getStorageValue(fromList, bounds.to + 1),
836 bounds.fTo));
837 }
838 }
839 catch (SamplingException se)
840 {
841
842 throw new RuntimeException("Error while obtaining subset of trajectory.", se);
843 }
844 out.extendedData.put(extendedDataType, toList);
845 }
846 }
847 return out;
848 }
849
850
851 @Override
852 public int hashCode()
853 {
854 final int prime = 31;
855 int result = 1;
856 result = prime * result + ((this.gtuId == null) ? 0 : this.gtuId.hashCode());
857 result = prime * result + this.size;
858 if (this.size > 0)
859 {
860 result = prime * result + Float.floatToIntBits(this.t[0]);
861 }
862 return result;
863 }
864
865
866 @Override
867 public boolean equals(final Object obj)
868 {
869 if (this == obj)
870 {
871 return true;
872 }
873 if (obj == null)
874 {
875 return false;
876 }
877 if (getClass() != obj.getClass())
878 {
879 return false;
880 }
881 Trajectory<?> other = (Trajectory<?>) obj;
882 if (this.size != other.size)
883 {
884 return false;
885 }
886 if (this.gtuId == null)
887 {
888 if (other.gtuId != null)
889 {
890 return false;
891 }
892 }
893 else if (!this.gtuId.equals(other.gtuId))
894 {
895 return false;
896 }
897 if (this.size > 0)
898 {
899 if (this.t[0] != other.t[0])
900 {
901 return false;
902 }
903 }
904 return true;
905 }
906
907
908 @Override
909 public String toString()
910 {
911 if (this.size > 0)
912 {
913 return "Trajectory [size=" + this.size + ", x={" + this.x[0] + "..." + this.x[this.size - 1] + "}, t={" + this.t[0]
914 + "..." + this.t[this.size - 1] + "}, metaData=" + this.metaData + ", gtuId=" + this.gtuId + "]";
915 }
916 return "Trajectory [size=" + this.size + ", x={}, t={}, metaData=" + this.metaData + ", gtuId=" + this.gtuId + "]";
917 }
918
919
920
921
922
923
924
925
926
927
928
929
930 public class Boundary
931 {
932
933 @SuppressWarnings("checkstyle:visibilitymodifier")
934 public final int index;
935
936
937 @SuppressWarnings("checkstyle:visibilitymodifier")
938 public final double fraction;
939
940
941
942
943
944 Boundary(final int index, final double fraction)
945 {
946 this.index = index;
947 this.fraction = fraction;
948 }
949
950
951 @Override
952 public final String toString()
953 {
954 return "Boundary [index=" + this.index + ", fraction=" + this.fraction + "]";
955 }
956
957
958
959
960
961
962 public double getValue(final float[] array)
963 {
964 if (this.fraction == 0.0)
965 {
966 return array[this.index];
967 }
968 if (this.fraction == 1.0)
969 {
970 return array[this.index + 1];
971 }
972 return (1 - this.fraction) * array[this.index] + this.fraction * array[this.index + 1];
973 }
974 }
975
976
977
978
979
980
981
982
983
984
985
986
987 private class Boundaries
988 {
989
990 @SuppressWarnings("checkstyle:visibilitymodifier")
991 public final int from;
992
993
994 @SuppressWarnings("checkstyle:visibilitymodifier")
995 public final double fFrom;
996
997
998 @SuppressWarnings("checkstyle:visibilitymodifier")
999 public final int to;
1000
1001
1002 @SuppressWarnings("checkstyle:visibilitymodifier")
1003 public final double fTo;
1004
1005
1006
1007
1008
1009
1010
1011 Boundaries(final int from, final double fFrom, final int to, final double fTo)
1012 {
1013 Throw.when(from < 0 || from > Trajectory.this.size() - 1, IllegalArgumentException.class,
1014 "Argument from (%d) is out of bounds.", from);
1015 Throw.when(fFrom < 0 || fFrom > 1, IllegalArgumentException.class, "Argument fFrom (%f) is out of bounds.", fFrom);
1016 Throw.when(from == Trajectory.this.size() && fFrom > 0, IllegalArgumentException.class,
1017 "Arguments from (%d) and fFrom (%f) are out of bounds.", from, fFrom);
1018 Throw.when(to < 0 || to >= Trajectory.this.size(), IllegalArgumentException.class,
1019 "Argument to (%d) is out of bounds.", to);
1020 Throw.when(fTo < 0 || fTo > 1, IllegalArgumentException.class, "Argument fTo (%f) is out of bounds.", fTo);
1021 Throw.when(to == Trajectory.this.size() && fTo > 0, IllegalArgumentException.class,
1022 "Arguments to (%d) and fTo (%f) are out of bounds.", to, fTo);
1023 this.from = from;
1024 this.fFrom = fFrom;
1025 this.to = to;
1026 this.fTo = fTo;
1027 }
1028
1029
1030
1031
1032
1033 public Boundaries intersect(final Boundaries boundaries)
1034 {
1035 if (this.to < boundaries.from || boundaries.to < this.from
1036 || this.to == boundaries.from && this.fTo < boundaries.fFrom
1037 || boundaries.to == this.from && boundaries.fTo < this.fFrom)
1038 {
1039 return new Boundaries(0, 0.0, 0, 0.0);
1040 }
1041 int newFrom;
1042 double newFFrom;
1043 if (this.from > boundaries.from || this.from == boundaries.from && this.fFrom > boundaries.fFrom)
1044 {
1045 newFrom = this.from;
1046 newFFrom = this.fFrom;
1047 }
1048 else
1049 {
1050 newFrom = boundaries.from;
1051 newFFrom = boundaries.fFrom;
1052 }
1053 int newTo;
1054 double newFTo;
1055 if (this.to < boundaries.to || this.to == boundaries.to && this.fTo < boundaries.fTo)
1056 {
1057 newTo = this.to;
1058 newFTo = this.fTo;
1059 }
1060 else
1061 {
1062 newTo = boundaries.to;
1063 newFTo = boundaries.fTo;
1064 }
1065 return new Boundaries(newFrom, newFFrom, newTo, newFTo);
1066 }
1067
1068
1069 @Override
1070 public final String toString()
1071 {
1072 return "Boundaries [from=" + this.from + ", fFrom=" + this.fFrom + ", to=" + this.to + ", fTo=" + this.fTo + "]";
1073 }
1074
1075 }
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089 public static class SpaceTimeView
1090 {
1091
1092
1093 final Length distance;
1094
1095
1096 final Duration time;
1097
1098
1099
1100
1101
1102
1103 private SpaceTimeView(final Length distance, final Duration time)
1104 {
1105 this.distance = distance;
1106 this.time = time;
1107 }
1108
1109
1110
1111
1112
1113 public final Length getDistance()
1114 {
1115 return this.distance;
1116 }
1117
1118
1119
1120
1121
1122 public final Duration getTime()
1123 {
1124 return this.time;
1125 }
1126
1127
1128 @Override
1129 public String toString()
1130 {
1131 return "SpaceTimeView [distance=" + this.distance + ", time=" + this.time + "]";
1132 }
1133 }
1134
1135 }