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 }