View Javadoc
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   * Contains position, speed, acceleration and time data of a GTU, over some section. Position is relative to the start of the
32   * lane in the direction of travel, also when trajectories have been truncated at a position x > 0. Note that this regards
33   * internal data and output. Input position always refers to the design line of the lane. This class internally flips input
34   * positions and boundaries.
35   * <p>
36   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
37   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
38   * </p>
39   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
40   * initial version Sep 21, 2016 <br>
41   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
42   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
43   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
44   * @param <G> gtu data type
45   */
46  // TODO some trajectories have the first time stamp twice, with possibly different values for acceleration (and TTC)
47  public final class Trajectory<G extends GtuDataInterface>
48  {
49  
50      /** Default array capacity. */
51      private static final int DEFAULT_CAPACITY = 10;
52  
53      /** Effective length of the underlying data (arrays may be longer). */
54      private int size = 0;
55  
56      /**
57       * Position array. Position is relative to the start of the lane in the direction of travel, also when trajectories have
58       * been truncated at a position x &gt; 0.
59       */
60      private float[] x = new float[DEFAULT_CAPACITY];
61  
62      /** Speed array. */
63      private float[] v = new float[DEFAULT_CAPACITY];
64  
65      /** Acceleration array. */
66      private float[] a = new float[DEFAULT_CAPACITY];
67  
68      /** Time array. */
69      private float[] t = new float[DEFAULT_CAPACITY];
70  
71      /** GTU id. */
72      private final String gtuId;
73  
74      /** Meta data. */
75      private final MetaData metaData;
76  
77      /** Map of array data types and their values. */
78      private final Map<ExtendedDataType<?, ?, ?, G>, Object> extendedData = new LinkedHashMap<>();
79  
80      /** Direction of travel. */
81      private final KpiLaneDirection kpiLaneDirection;
82  
83      /**
84       * @param gtu GtuDataInterface; GTU of this trajectory, only the id is stored.
85       * @param metaData MetaData; meta data
86       * @param extendedData Set&lt;ExtendedDataType&lt;?,?,?,G&gt;&gt;; types of extended data
87       * @param kpiLaneDirection KpiLaneDirection; direction of travel
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       * Private constructor for creating subsets.
97       * @param gtuId String; GTU id
98       * @param metaData MetaData; meta data
99       * @param extendedData Set&lt;ExtendedDataType&lt;?,?,?,G&gt;&gt;; types of extended data
100      * @param kpiLaneDirection KpiLaneDirection; direction of travel
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      * Adds values of position, speed, acceleration and time.
120      * @param position Length; position is relative to the start of the lane in the direction of the design line, i.e.
121      *            irrespective of the travel direction, also when trajectories have been truncated at a position x &gt; 0
122      * @param speed Speed; speed
123      * @param acceleration Acceleration; acceleration
124      * @param time Time; time
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      * Adds values of position, speed, acceleration and time.
133      * @param position Length; position is relative to the start of the lane in the direction of the design line, i.e.
134      *            irrespective of the travel direction, also when trajectories have been truncated at a position x &gt; 0
135      * @param speed Speed; speed
136      * @param acceleration Acceleration; acceleration
137      * @param time Time; time
138      * @param gtu G; gtu to add extended data for
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      * @param extendedDataType ExtendedDataType&lt;T,?,S,G&gt;; extended data type
168      * @param gtu G; gtu
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      * @return size of the underlying trajectory data
183      */
184     public int size()
185     {
186         return this.size;
187     }
188 
189     /**
190      * @return GTU id
191      */
192     public String getGtuId()
193     {
194         return this.gtuId;
195     }
196 
197     /**
198      * @return si position values, position is relative to the start of the lane, also when trajectories have been truncated at
199      *         a position x &gt; 0
200      */
201     public float[] getX()
202     {
203         return Arrays.copyOf(this.x, this.size);
204     }
205 
206     /**
207      * @return si speed values
208      */
209     public float[] getV()
210     {
211         return Arrays.copyOf(this.v, this.size);
212     }
213 
214     /**
215      * @return si acceleration values
216      */
217     public float[] getA()
218     {
219         return Arrays.copyOf(this.a, this.size);
220     }
221 
222     /**
223      * @return si time values
224      */
225     public float[] getT()
226     {
227         return Arrays.copyOf(this.t, this.size);
228     }
229 
230     /**
231      * Returns the last index with a position smaller than or equal to the given position.
232      * @param position float; position
233      * @return int; last index with a position smaller than or equal to the given position
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      * Returns the last index with a time smaller than or equal to the given time.
247      * @param time float; time
248      * @return int; last index with a time smaller than or equal to the given time
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      * Returns {@code x} value of a single sample.
262      * @param index int; index
263      * @return {@code x} value of a single sample
264      * @throws SamplingException if the index is out of bounds
265      */
266     public float getX(final int index) throws SamplingException
267     {
268         checkSample(index);
269         return this.x[index];
270     }
271 
272     /**
273      * Returns {@code v} value of a single sample.
274      * @param index int; index
275      * @return {@code v} value of a single sample
276      * @throws SamplingException if the index is out of bounds
277      */
278     public float getV(final int index) throws SamplingException
279     {
280         checkSample(index);
281         return this.v[index];
282     }
283 
284     /**
285      * Returns {@code a} value of a single sample.
286      * @param index int; index
287      * @return {@code a} value of a single sample
288      * @throws SamplingException if the index is out of bounds
289      */
290     public float getA(final int index) throws SamplingException
291     {
292         checkSample(index);
293         return this.a[index];
294     }
295 
296     /**
297      * Returns {@code t} value of a single sample.
298      * @param index int; index
299      * @return {@code t} value of a single sample
300      * @throws SamplingException if the index is out of bounds
301      */
302     public float getT(final int index) throws SamplingException
303     {
304         checkSample(index);
305         return this.t[index];
306     }
307 
308     /**
309      * Returns extended data type value of a single sample.
310      * @param extendedDataType ExtendedDataType&lt;T,?,S,?&gt;; data type from which to retrieve the data
311      * @param index int; index for which to retrieve the data
312      * @param <T> scalar type of extended data type
313      * @param <S> storage type of extended data type
314      * @return extended data type value of a single sample
315      * @throws SamplingException if the index is out of bounds
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      * Throws an exception if the sample index is out of bounds.
327      * @param index int; sample index
328      * @throws SamplingException if the sample index is out of bounds
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      * @return strongly typed copy of position, position is relative to the start of the lane, also when trajectories have been
337      *         truncated at a position x &gt; 0
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             // should not happen, inputs are not null
348             throw new RuntimeException("Could not return trajectory data.", exception);
349         }
350     }
351 
352     /**
353      * @return strongly typed copy of speed
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             // should not happen, inputs are not null
364             throw new RuntimeException("Could not return trajectory data.", exception);
365         }
366     }
367 
368     /**
369      * @return strongly typed copy of acceleration
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             // should not happen, inputs are not null
380             throw new RuntimeException("Could not return trajectory data.", exception);
381         }
382     }
383 
384     /**
385      * @return strongly typed copy of time
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             // should not happen, inputs are not null
396             throw new RuntimeException("Could not return trajectory data.", exception);
397         }
398     }
399 
400     /**
401      * @return total length of this trajectory
402      * @throws IllegalStateException if trajectory is empty
403      */
404     public Length getTotalLength()
405     {
406         // TODO do not allow empty trajectory
407         // Throw.when(this.size == 0, IllegalStateException.class, "Empty trajectory does not have a length.");
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      * @return total duration of this trajectory
417      * @throws IllegalStateException if trajectory is empty
418      */
419     public Duration getTotalDuration()
420     {
421         // TODO do not allow empty trajectory
422         // Throw.when(this.size == 0, IllegalStateException.class, "Empty trajectory does not have a duration.");
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      * @param metaDataType MetaDataType&lt;?&gt;; meta data type
432      * @return whether the trajectory contains the meta data of give type
433      */
434     public boolean contains(final MetaDataType<?> metaDataType)
435     {
436         return this.metaData.contains(metaDataType);
437     }
438 
439     /**
440      * @param metaDataType MetaDataType&lt;T&gt;; meta data type
441      * @param <T> class of meta data
442      * @return value of meta data
443      */
444     public <T> T getMetaData(final MetaDataType<T> metaDataType)
445     {
446         return this.metaData.get(metaDataType);
447     }
448 
449     /**
450      * Returns the included meta data types.
451      * @return included meta data types
452      */
453     public Set<MetaDataType<?>> getMetaDataTypes()
454     {
455         return this.metaData.getMetaDataTypes();
456     }
457 
458     /**
459      * @param extendedDataType ExtendedDataType&lt;?,?,?,?&gt;; extended data type
460      * @return whether the trajectory contains the extended data of give type
461      */
462     public boolean contains(final ExtendedDataType<?, ?, ?, ?> extendedDataType)
463     {
464         return this.extendedData.containsKey(extendedDataType);
465     }
466 
467     /**
468      * @param extendedDataType ExtendedDataType&lt;?,O,S,?&gt;; extended data type to return
469      * @param <O> output type
470      * @param <S> storage type
471      * @return values of extended data type
472      * @throws SamplingException if the extended data type is not in the trajectory
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      * Returns the included extended data types.
484      * @return included extended data types
485      */
486     public Set<ExtendedDataType<?, ?, ?, G>> getExtendedDataTypes()
487     {
488         return this.extendedData.keySet();
489     }
490 
491     /**
492      * Returns a space-time view of this trajectory. This is much more efficient than {@code subSet()} as no trajectory is
493      * copied. The limitation is that only distance and time (and mean speed) in the space-time view can be obtained.
494      * @param startPosition Length; start position
495      * @param endPosition Length; end position
496      * @param startTime Time; start time
497      * @param endTime Time; end time
498      * @return space-time view of this trajectory
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      * Copies the trajectory but with a subset of the data. Longitudinal entry is only true if the original trajectory has true,
540      * and the subset is from the start.
541      * @param startPosition Length; start position
542      * @param endPosition Length; end position
543      * @return subset of the trajectory
544      * @throws NullPointerException if an input is null
545      * @throws IllegalArgumentException of minLength is smaller than maxLength
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      * Copies the trajectory but with a subset of the data.
564      * @param startTime Time; start time
565      * @param endTime Time; end time
566      * @return subset of the trajectory
567      * @throws NullPointerException if an input is null
568      * @throws IllegalArgumentException of minTime is smaller than maxTime
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      * Copies the trajectory but with a subset of the data.
584      * @param startPosition Length; start position
585      * @param endPosition Length; end position
586      * @param startTime Time; start time
587      * @param endTime Time; end time
588      * @return subset of the trajectory
589      * @throws NullPointerException if an input is null
590      * @throws IllegalArgumentException of minLength/Time is smaller than maxLength/Time
591      */
592     public Trajectory<G> subSet(final Length startPosition, final Length endPosition, final Time startTime, final Time endTime)
593     {
594         // could use this.subSet(minLength, maxLength).subSet(minTime, maxTime), but that copies twice
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      * Determine spatial boundaries.
613      * @param startPosition Length; start position
614      * @param endPosition Length; end position
615      * @return spatial boundaries
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         // to float needed as x is in floats and due to precision fTo > 1 may become true
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         // int from = binarySearchX(startPos);
630         // double fFrom = 0;
631         // if (this.x[from] < startPos)
632         // {
633         // fFrom = (startPos - this.x[from]) / (this.x[from + 1] - this.x[from]);
634         // }
635         // int to = binarySearchX(endPos);
636         // double fTo = 0;
637         // if (to < this.size - 1)
638         // {
639         // fTo = (endPos - this.x[to]) / (this.x[to + 1] - this.x[to]);
640         // }
641         // return new Boundaries(from, fFrom, to, fTo);
642     }
643 
644     /**
645      * Determine temporal boundaries.
646      * @param startTime Time; start time
647      * @param endTime Time; end time
648      * @return spatial boundaries
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         // to float needed as x is in floats and due to precision fTo > 1 may become true
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         // int from = binarySearchT(startTim);
663         // double fFrom = 0;
664         // if (this.t[from] < startTim)
665         // {
666         // fFrom = (startTim - this.t[from]) / (this.t[from + 1] - this.t[from]);
667         // }
668         // int to = binarySearchT(endTim);
669         // double fTo = 0;
670         // if (to < this.size - 1)
671         // {
672         // fTo = (endTim - this.t[to]) / (this.t[to + 1] - this.t[to]);
673         // }
674         // return new Boundaries(from, fFrom, to, fTo);
675     }
676 
677     /**
678      * Returns the boundary at the given position.
679      * @param position float; position
680      * @param end boolean; whether the end of a range is searched
681      * @return Boundary; boundary at the given position
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      * Returns the boundary at the given time.
696      * @param time float; time
697      * @param end boolean; whether the end of a range is searched
698      * @return Boundary; boundary at the given time
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      * Returns an interpolated time at the given position.
713      * @param position Length; position
714      * @return Time; interpolated time at the given position
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      * Returns an interpolated speed at the given position.
723      * @param position Length; position
724      * @return Speed; interpolated speed at the given position
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      * Returns an interpolated acceleration at the given position.
733      * @param position Length; position
734      * @return Acceleration; interpolated acceleration at the given position
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      * Returns an interpolated position at the given time.
743      * @param time Time; time
744      * @return Length; interpolated position at the given time
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      * Returns an interpolated speed at the given time.
753      * @param time Time; time
754      * @return Speed; interpolated speed at the given time
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      * Returns an interpolated acceleration at the given time.
763      * @param time Time; time
764      * @return Acceleration; interpolated acceleration at the given time
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      * Copies the trajectory but with a subset of the data. Data is taken from position (from + fFrom) to (to + fTo).
773      * @param bounds Boundaries; boundaries
774      * @param <T> type of underlying extended data value
775      * @param <S> storage type
776      * @return subset of the trajectory
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) // otherwise empty, no data in the subset
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                     // should not happen as bounds are determined internally
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
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      * <p>
921      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
922      * <br>
923      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
924      * <p>
925      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 15 okt. 2018 <br>
926      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
927      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
928      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
929      */
930     public class Boundary
931     {
932         /** Rounded-down index. */
933         @SuppressWarnings("checkstyle:visibilitymodifier")
934         public final int index;
935 
936         /** Fraction. */
937         @SuppressWarnings("checkstyle:visibilitymodifier")
938         public final double fraction;
939 
940         /**
941          * @param index int; rounded down index
942          * @param fraction double; fraction
943          */
944         Boundary(final int index, final double fraction)
945         {
946             this.index = index;
947             this.fraction = fraction;
948         }
949 
950         /** {@inheritDoc} */
951         @Override
952         public final String toString()
953         {
954             return "Boundary [index=" + this.index + ", fraction=" + this.fraction + "]";
955         }
956 
957         /**
958          * Returns the value at the boundary in the array.
959          * @param array float[]; float[] array
960          * @return double; value at the boundary in the array
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      * <p>
978      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
979      * <br>
980      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
981      * <p>
982      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 12 okt. 2016 <br>
983      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
984      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
985      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
986      */
987     private class Boundaries
988     {
989         /** Rounded-down from-index. */
990         @SuppressWarnings("checkstyle:visibilitymodifier")
991         public final int from;
992 
993         /** Fraction of to-index. */
994         @SuppressWarnings("checkstyle:visibilitymodifier")
995         public final double fFrom;
996 
997         /** Rounded-down to-index. */
998         @SuppressWarnings("checkstyle:visibilitymodifier")
999         public final int to;
1000 
1001         /** Fraction of to-index. */
1002         @SuppressWarnings("checkstyle:visibilitymodifier")
1003         public final double fTo;
1004 
1005         /**
1006          * @param from int; from index, rounded down
1007          * @param fFrom double; from index, fraction
1008          * @param to int; to index, rounded down
1009          * @param fTo double; to index, fraction
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          * @param boundaries Boundaries; boundaries
1031          * @return intersection of both boundaries
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); // no overlap
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         /** {@inheritDoc} */
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      * Space-time view of a trajectory. This supplies distance and time (and mean speed) in a space-time box.
1079      * <p>
1080      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1081      * <br>
1082      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
1083      * <p>
1084      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 5 okt. 2018 <br>
1085      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
1086      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
1087      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
1088      */
1089     public static class SpaceTimeView
1090     {
1091 
1092         /** Distance. */
1093         final Length distance;
1094 
1095         /** Time. */
1096         final Duration time;
1097 
1098         /**
1099          * Constructor.
1100          * @param distance Length; distance
1101          * @param time Duration; time
1102          */
1103         private SpaceTimeView(final Length distance, final Duration time)
1104         {
1105             this.distance = distance;
1106             this.time = time;
1107         }
1108 
1109         /**
1110          * Returns the distance.
1111          * @return Length; distance
1112          */
1113         public final Length getDistance()
1114         {
1115             return this.distance;
1116         }
1117 
1118         /**
1119          * Returns the time.
1120          * @return Duration; time
1121          */
1122         public final Duration getTime()
1123         {
1124             return this.time;
1125         }
1126 
1127         /** {@inheritDoc} */
1128         @Override
1129         public String toString()
1130         {
1131             return "SpaceTimeView [distance=" + this.distance + ", time=" + this.time + "]";
1132         }
1133     }
1134 
1135 }