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