View Javadoc
1   package org.opentrafficsim.kpi.sampling;
2   
3   import java.util.Arrays;
4   import java.util.HashMap;
5   import java.util.Map;
6   import java.util.Set;
7   
8   import org.djunits.unit.AccelerationUnit;
9   import org.djunits.unit.LengthUnit;
10  import org.djunits.unit.SpeedUnit;
11  import org.djunits.unit.TimeUnit;
12  import org.djunits.value.StorageType;
13  import org.djunits.value.ValueException;
14  import org.djunits.value.vdouble.scalar.Acceleration;
15  import org.djunits.value.vdouble.scalar.Duration;
16  import org.djunits.value.vdouble.scalar.Length;
17  import org.djunits.value.vdouble.scalar.Speed;
18  import org.djunits.value.vdouble.scalar.Time;
19  import org.djunits.value.vfloat.vector.FloatAccelerationVector;
20  import org.djunits.value.vfloat.vector.FloatLengthVector;
21  import org.djunits.value.vfloat.vector.FloatSpeedVector;
22  import org.djunits.value.vfloat.vector.FloatTimeVector;
23  import org.opentrafficsim.kpi.interfaces.GtuDataInterface;
24  import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
25  import org.opentrafficsim.kpi.sampling.meta.MetaData;
26  import org.opentrafficsim.kpi.sampling.meta.MetaDataType;
27  
28  import nl.tudelft.simulation.language.Throw;
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-2016 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   */
45  // TODO some trajectories have the first time stamp twice, with possibly different values for acceleration (and TTC)
46  public final class Trajectory
47  {
48  
49      /** Default array capacity. */
50      private static final int DEFAULT_CAPACITY = 10;
51  
52      /** Effective length of the underlying data (arrays may be longer). */
53      private int size = 0;
54  
55      /**
56       * Position array. Position is relative to the start of the lane in the direction of travel, also when trajectories have
57       * been truncated at a position x &gt; 0.
58       */
59      private float[] x = new float[DEFAULT_CAPACITY];
60  
61      /** Speed array. */
62      private float[] v = new float[DEFAULT_CAPACITY];
63  
64      /** Acceleration array. */
65      private float[] a = new float[DEFAULT_CAPACITY];
66  
67      /** Time array. */
68      private float[] t = new float[DEFAULT_CAPACITY];
69  
70      /** GTU id. */
71      private final String gtuId;
72  
73      /** Meta data. */
74      private final MetaData metaData;
75  
76      /** Map of array data types and their values. */
77      private final Map<ExtendedDataType<?, ?, ?>, Object> extendedData = new HashMap<>();
78  
79      /** Direction of travel. */
80      private final KpiLaneDirection kpiLaneDirection;
81  
82      /**
83       * @param gtu GTU of this trajectory, only the id is stored.
84       * @param metaData meta data
85       * @param extendedData types of extended data
86       * @param kpiLaneDirection direction of travel
87       */
88      public Trajectory(final GtuDataInterface gtu, final MetaData metaData, final Set<ExtendedDataType<?, ?, ?>> extendedData,
89              final KpiLaneDirection kpiLaneDirection)
90      {
91          this(gtu == null ? null : gtu.getId(), metaData, extendedData, kpiLaneDirection);
92      }
93  
94      /**
95       * Private constructor for creating subsets.
96       * @param gtuId GTU id
97       * @param metaData meta data
98       * @param extendedData types of extended data
99       * @param kpiLaneDirection direction of travel
100      */
101     private Trajectory(final String gtuId, final MetaData metaData, final Set<ExtendedDataType<?, ?, ?>> extendedData,
102             final KpiLaneDirection kpiLaneDirection)
103     {
104         Throw.whenNull(gtuId, "GTU may not be null.");
105         Throw.whenNull(metaData, "Meta data may not be null.");
106         Throw.whenNull(extendedData, "Extended data may not be null.");
107         Throw.whenNull(kpiLaneDirection, "Lane direction may not be null.");
108         this.gtuId = gtuId;
109         this.metaData = new MetaData(metaData);
110         for (ExtendedDataType<?, ?, ?> dataType : extendedData)
111         {
112             this.extendedData.put(dataType, dataType.initializeStorage());
113         }
114         this.kpiLaneDirection = kpiLaneDirection;
115     }
116 
117     /**
118      * Adds values of position, speed, acceleration and time.
119      * @param position position is relative to the start of the lane in the direction of the design line, i.e. irrespective of
120      *            the travel direction, also when trajectories have been truncated at a position x &gt; 0
121      * @param speed speed
122      * @param acceleration acceleration
123      * @param time time
124      */
125     public void add(final Length position, final Speed speed, final Acceleration acceleration, final Time time)
126     {
127         add(position, speed, acceleration, time, null);
128     }
129 
130     /**
131      * Adds values of position, speed, acceleration and time.
132      * @param position position is relative to the start of the lane in the direction of the design line, i.e. irrespective of
133      *            the travel direction, also when trajectories have been truncated at a position x &gt; 0
134      * @param speed speed
135      * @param acceleration acceleration
136      * @param time time
137      * @param gtu gtu to add extended data for
138      */
139     public void add(final Length position, final Speed speed, final Acceleration acceleration, final Time time,
140             final GtuDataInterface 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<?, ?, ?> extendedDataType : this.extendedData.keySet())
160         {
161             appendValue(extendedDataType, gtu);
162         }
163         this.size++;
164     }
165 
166     /**
167      * @param extendedDataType extended data type
168      * @param gtu gtu
169      */
170     @SuppressWarnings("unchecked")
171     private <T, S> void appendValue(final ExtendedDataType<?, ?, ?> extendedDataType, final GtuDataInterface gtu)
172     {
173         ExtendedDataType<T, ?, S> edt = (ExtendedDataType<T, ?, S>) extendedDataType;
174         S in = (S) this.extendedData.get(edt);
175         S out = edt.setValue(in, this.size, edt.getValue(gtu));
176         if (in != out)
177         {
178             this.extendedData.put(edt, out);
179         }
180     }
181 
182     /**
183      * @return size of the underlying trajectory data
184      */
185     public int size()
186     {
187         return this.size;
188     }
189 
190     /**
191      * @return GTU id
192      */
193     public String getGtuId()
194     {
195         return this.gtuId;
196     }
197 
198     /**
199      * @return si position values, position is relative to the start of the lane, also when trajectories have been truncated at
200      *         a position x &gt; 0
201      */
202     public float[] getX()
203     {
204         return Arrays.copyOf(this.x, this.size);
205     }
206 
207     /**
208      * @return si speed values
209      */
210     public float[] getV()
211     {
212         return Arrays.copyOf(this.v, this.size);
213     }
214 
215     /**
216      * @return si acceleration values
217      */
218     public float[] getA()
219     {
220         return Arrays.copyOf(this.a, this.size);
221     }
222 
223     /**
224      * @return si time values
225      */
226     public float[] getT()
227     {
228         return Arrays.copyOf(this.t, this.size);
229     }
230 
231     /**
232      * @return strongly typed copy of position, position is relative to the start of the lane, also when trajectories have been
233      *         truncated at a position x &gt; 0
234      */
235     public FloatLengthVector getPosition()
236     {
237         try
238         {
239             return new FloatLengthVector(getX(), LengthUnit.SI, StorageType.DENSE);
240         }
241         catch (ValueException exception)
242         {
243             // should not happen, inputs are not null
244             throw new RuntimeException("Could not return trajectory data.", exception);
245         }
246     }
247 
248     /**
249      * @return strongly typed copy of speed
250      */
251     public FloatSpeedVector getSpeed()
252     {
253         try
254         {
255             return new FloatSpeedVector(getV(), SpeedUnit.SI, StorageType.DENSE);
256         }
257         catch (ValueException exception)
258         {
259             // should not happen, inputs are not null
260             throw new RuntimeException("Could not return trajectory data.", exception);
261         }
262     }
263 
264     /**
265      * @return strongly typed copy of acceleration
266      */
267     public FloatAccelerationVector getAcceleration()
268     {
269         try
270         {
271             return new FloatAccelerationVector(getA(), AccelerationUnit.SI, StorageType.DENSE);
272         }
273         catch (ValueException exception)
274         {
275             // should not happen, inputs are not null
276             throw new RuntimeException("Could not return trajectory data.", exception);
277         }
278     }
279 
280     /**
281      * @return strongly typed copy of time
282      */
283     public FloatTimeVector getTime()
284     {
285         try
286         {
287             return new FloatTimeVector(getT(), TimeUnit.SI, StorageType.DENSE);
288         }
289         catch (ValueException exception)
290         {
291             // should not happen, inputs are not null
292             throw new RuntimeException("Could not return trajectory data.", exception);
293         }
294     }
295 
296     /**
297      * @return total length of this trajectory
298      * @throws IllegalStateException if trajectory is empty
299      */
300     public Length getTotalLength()
301     {
302         // TODO do not allow empty trajectory
303         // Throw.when(this.size == 0, IllegalStateException.class, "Empty trajectory does not have a length.");
304         if (this.size == 0)
305         {
306             return Length.ZERO;
307         }
308         return new Length(this.x[this.size - 1] - this.x[0], LengthUnit.SI);
309     }
310 
311     /**
312      * @return total duration of this trajectory
313      * @throws IllegalStateException if trajectory is empty
314      */
315     public Duration getTotalDuration()
316     {
317         // TODO do not allow empty trajectory
318         // Throw.when(this.size == 0, IllegalStateException.class, "Empty trajectory does not have a duration.");
319         if (this.size == 0)
320         {
321             return Duration.ZERO;
322         }
323         return new Duration(this.t[this.size - 1] - this.t[0], TimeUnit.SI);
324     }
325 
326     /**
327      * @param metaDataType meta data type
328      * @return whether the trajectory contains the meta data of give type
329      */
330     public boolean contains(final MetaDataType<?> metaDataType)
331     {
332         return this.metaData.contains(metaDataType);
333     }
334 
335     /**
336      * @param metaDataType meta data type
337      * @param <T> class of meta data
338      * @return value of meta data
339      */
340     public <T> T getMetaData(final MetaDataType<T> metaDataType)
341     {
342         return this.metaData.get(metaDataType);
343     }
344 
345     /**
346      * Returns the included meta data types.
347      * @return included meta data types
348      */
349     public Set<MetaDataType<?>> getMetaDataTypes()
350     {
351         return this.metaData.getMetaDataTypes();
352     }
353 
354     /**
355      * @param extendedDataType extended data type
356      * @return whether the trajectory contains the extended data of give type
357      */
358     public boolean contains(final ExtendedDataType<?, ?, ?> extendedDataType)
359     {
360         return this.extendedData.containsKey(extendedDataType);
361     }
362 
363     /**
364      * @param extendedDataType extended data type to return
365      * @param <O> output type
366      * @param <S> storage type
367      * @return values of extended data type
368      * @throws SamplingException if the extended data type is not in the trajectory
369      */
370     @SuppressWarnings("unchecked")
371     public <O, S> O getExtendedData(final ExtendedDataType<?, O, S> extendedDataType) throws SamplingException
372     {
373         Throw.when(!this.extendedData.containsKey(extendedDataType), SamplingException.class,
374                 "Extended data type %s is not in the trajectory.", extendedDataType);
375         return extendedDataType.convert((S) this.extendedData.get(extendedDataType), this.size);
376     }
377 
378     /**
379      * Returns the included extended data types.
380      * @return included extended data types
381      */
382     public Set<ExtendedDataType<?, ?, ?>> getExtendedDataTypes()
383     {
384         return this.extendedData.keySet();
385     }
386 
387     /**
388      * Copies the trajectory but with a subset of the data. Longitudinal entry is only true if the original trajectory has true,
389      * and the subset is from the start.
390      * @param startPosition start position
391      * @param endPosition end position
392      * @return subset of the trajectory
393      * @throws NullPointerException if an input is null
394      * @throws IllegalArgumentException of minLength is smaller than maxLength
395      */
396     public Trajectory subSet(final Length startPosition, final Length endPosition)
397     {
398         Throw.whenNull(startPosition, "Start position may not be null");
399         Throw.whenNull(endPosition, "End position may not be null");
400         Length length0 = this.kpiLaneDirection.getPositionInDirection(startPosition);
401         Length length1 = this.kpiLaneDirection.getPositionInDirection(endPosition);
402         Throw.when(length0.gt(length1), IllegalArgumentException.class,
403                 "Start position should be smaller than end position in the direction of travel");
404         return subSet(spaceBoundaries(length0, length1));
405     }
406 
407     /**
408      * Copies the trajectory but with a subset of the data. Longitudinal entry is only true if the original trajectory has true,
409      * and the subset is from the start.
410      * @param startTime start time
411      * @param endTime end time
412      * @return subset of the trajectory
413      * @throws NullPointerException if an input is null
414      * @throws IllegalArgumentException of minTime is smaller than maxTime
415      */
416     public Trajectory subSet(final Time startTime, final Time endTime)
417     {
418         Throw.whenNull(startTime, "Start time may not be null");
419         Throw.whenNull(endTime, "End time may not be null");
420         Throw.when(startTime.gt(endTime), IllegalArgumentException.class, "Start time should be smaller than end time.");
421         return subSet(timeBoundaries(startTime, endTime));
422     }
423 
424     /**
425      * Copies the trajectory but with a subset of the data. Longitudinal entry is only true if the original trajectory has true,
426      * and the subset is from the start.
427      * @param startPosition start position
428      * @param endPosition end position
429      * @param startTime start time
430      * @param endTime end time
431      * @return subset of the trajectory
432      * @throws NullPointerException if an input is null
433      * @throws IllegalArgumentException of minLength/Time is smaller than maxLength/Time
434      */
435     public Trajectory subSet(final Length startPosition, final Length endPosition, final Time startTime, final Time endTime)
436     {
437         // could use this.subSet(minLength, maxLength).subSet(minTime, maxTime), but that copies twice
438         Throw.whenNull(startPosition, "Start position may not be null");
439         Throw.whenNull(endPosition, "End position may not be null");
440         Length length0 = this.kpiLaneDirection.getPositionInDirection(startPosition);
441         Length length1 = this.kpiLaneDirection.getPositionInDirection(endPosition);
442         Throw.when(length0.gt(length1), IllegalArgumentException.class,
443                 "Start position should be smaller than end position in the direction of travel");
444         Throw.whenNull(startTime, "Start time may not be null");
445         Throw.whenNull(endTime, "End time may not be null");
446         Throw.when(startTime.gt(endTime), IllegalArgumentException.class, "Start time should be smaller than end time.");
447         return subSet(spaceBoundaries(length0, length1).intersect(timeBoundaries(startTime, endTime)));
448     }
449 
450     /**
451      * Determine spatial boundaries.
452      * @param startPosition start position
453      * @param endPosition end position
454      * @return spatial boundaries
455      */
456     private Boundaries spaceBoundaries(final Length startPosition, final Length endPosition)
457     {
458         if (startPosition.si > this.x[this.size - 1] || endPosition.si < this.x[0])
459         {
460             return new Boundaries(0, 0.0, 0, 0.0);
461         }
462         int from = 0;
463         double fFrom = 0;
464         // to float needed as x is in floats and due to precision fTo > 1 may become true
465         float startPos = (float) startPosition.si;
466         float endPos = (float) endPosition.si;
467         while (startPos > this.x[from + 1] && from < this.size - 1)
468         {
469             from++;
470         }
471         if (this.x[from] < startPos)
472         {
473             fFrom = (startPos - this.x[from]) / (this.x[from + 1] - this.x[from]);
474         }
475         int to = this.size - 1;
476         double fTo = 0;
477         while (endPos < this.x[to] && to > 0)
478         {
479             to--;
480         }
481         if (to < this.size - 1)
482         {
483             fTo = (endPos - this.x[to]) / (this.x[to + 1] - this.x[to]);
484         }
485         return new Boundaries(from, fFrom, to, fTo);
486     }
487 
488     /**
489      * Determine temporal boundaries.
490      * @param startTime start time
491      * @param endTime end time
492      * @return spatial boundaries
493      */
494     private Boundaries timeBoundaries(final Time startTime, final Time endTime)
495     {
496         if (startTime.si > this.t[this.size - 1] || endTime.si < this.t[0])
497         {
498             return new Boundaries(0, 0.0, 0, 0.0);
499         }
500         int from = 0;
501         double fFrom = 0;
502         // to float needed as x is in floats and due to precision fTo > 1 may become true
503         float startTim = (float) startTime.si;
504         float endTim = (float) endTime.si;
505         while (startTim > this.t[from + 1] && from < this.size - 1)
506         {
507             from++;
508         }
509         if (this.t[from] < startTim)
510         {
511             fFrom = (startTim - this.t[from]) / (this.t[from + 1] - this.t[from]);
512         }
513         int to = this.size - 1;
514         double fTo = 0;
515         while (endTim < this.t[to] && to > 0)
516         {
517             to--;
518         }
519         if (to < this.size - 1)
520         {
521             fTo = (endTim - this.t[to]) / (this.t[to + 1] - this.t[to]);
522         }
523         return new Boundaries(from, fFrom, to, fTo);
524     }
525 
526     /**
527      * Copies the trajectory but with a subset of the data. Data is taken from position (from + fFrom) to (to + fTo).
528      * @param bounds boundaries
529      * @param <T> type of underlying extended data value
530      * @param <S> storage type
531      * @return subset of the trajectory
532      */
533     @SuppressWarnings("unchecked")
534     private <T, S> Trajectory subSet(final Boundaries bounds)
535     {
536         Trajectory out = new Trajectory(this.gtuId, this.metaData, this.extendedData.keySet(), this.kpiLaneDirection);
537         if (bounds.from < bounds.to) // otherwise empty, no data in the subset
538         {
539             int nBefore = bounds.fFrom < 1.0 ? 1 : 0;
540             int nAfter = bounds.fTo > 0.0 ? 1 : 0;
541             int n = bounds.to - bounds.from + nBefore + nAfter;
542             out.x = new float[n];
543             out.v = new float[n];
544             out.a = new float[n];
545             out.t = new float[n];
546             System.arraycopy(this.x, bounds.from + 1, out.x, nBefore, bounds.to - bounds.from);
547             System.arraycopy(this.v, bounds.from + 1, out.v, nBefore, bounds.to - bounds.from);
548             System.arraycopy(this.a, bounds.from + 1, out.a, nBefore, bounds.to - bounds.from);
549             System.arraycopy(this.t, bounds.from + 1, out.t, nBefore, bounds.to - bounds.from);
550             if (nBefore == 1)
551             {
552                 out.x[0] = (float) (this.x[bounds.from] * (1 - bounds.fFrom) + this.x[bounds.from + 1] * bounds.fFrom);
553                 out.v[0] = (float) (this.v[bounds.from] * (1 - bounds.fFrom) + this.v[bounds.from + 1] * bounds.fFrom);
554                 out.a[0] = (float) (this.a[bounds.from] * (1 - bounds.fFrom) + this.a[bounds.from + 1] * bounds.fFrom);
555                 out.t[0] = (float) (this.t[bounds.from] * (1 - bounds.fFrom) + this.t[bounds.from + 1] * bounds.fFrom);
556             }
557             if (nAfter == 1)
558             {
559                 out.x[n - 1] = (float) (this.x[bounds.to] * (1 - bounds.fTo) + this.x[bounds.to + 1] * bounds.fTo);
560                 out.v[n - 1] = (float) (this.v[bounds.to] * (1 - bounds.fTo) + this.v[bounds.to + 1] * bounds.fTo);
561                 out.a[n - 1] = (float) (this.a[bounds.to] * (1 - bounds.fTo) + this.a[bounds.to + 1] * bounds.fTo);
562                 out.t[n - 1] = (float) (this.t[bounds.to] * (1 - bounds.fTo) + this.t[bounds.to + 1] * bounds.fTo);
563             }
564             out.size = n;
565             for (ExtendedDataType<?, ?, ?> extendedDataType : this.extendedData.keySet())
566             {
567                 int j = 0;
568                 ExtendedDataType<T, ?, S> edt = (ExtendedDataType<T, ?, S>) extendedDataType;
569                 S fromList = (S) this.extendedData.get(extendedDataType);
570                 S toList = edt.initializeStorage();
571                 try
572                 {
573                     if (nBefore == 1)
574                     {
575                         edt.setValue(toList, j,
576                                 ((ExtendedDataType<T, ?, ?>) extendedDataType).interpolate(
577                                         edt.getStorageValue(fromList, bounds.from),
578                                         edt.getStorageValue(fromList, bounds.from + 1), bounds.fFrom));
579                         j++;
580                     }
581                     for (int i = bounds.from + 1; i < bounds.to; i++)
582                     {
583                         edt.setValue(toList, j, edt.getStorageValue(fromList, i));
584                         j++;
585                     }
586                     if (nAfter == 1)
587                     {
588                         edt.setValue(toList, j,
589                                 ((ExtendedDataType<T, ?, ?>) extendedDataType).interpolate(
590                                         edt.getStorageValue(fromList, bounds.to), edt.getStorageValue(fromList, bounds.to + 1),
591                                         bounds.fTo));
592                     }
593                 }
594                 catch (SamplingException se)
595                 {
596                     // should not happen as bounds are determined internally
597                     throw new RuntimeException("Error while obtaining subset of trajectory.", se);
598                 }
599                 out.extendedData.put(extendedDataType, toList);
600             }
601         }
602         return out;
603     }
604 
605     /** {@inheritDoc} */
606     @Override
607     public int hashCode()
608     {
609         final int prime = 31;
610         int result = 1;
611         result = prime * result + ((this.gtuId == null) ? 0 : this.gtuId.hashCode());
612         result = prime * result + this.size;
613         if (this.size > 0)
614         {
615             result = prime * result + Float.floatToIntBits(this.t[0]);
616         }
617         return result;
618     }
619 
620     /** {@inheritDoc} */
621     @Override
622     public boolean equals(final Object obj)
623     {
624         if (this == obj)
625         {
626             return true;
627         }
628         if (obj == null)
629         {
630             return false;
631         }
632         if (getClass() != obj.getClass())
633         {
634             return false;
635         }
636         Trajectory other = (Trajectory) obj;
637         if (this.size != other.size)
638         {
639             return false;
640         }
641         if (this.gtuId == null)
642         {
643             if (other.gtuId != null)
644             {
645                 return false;
646             }
647         }
648         else if (!this.gtuId.equals(other.gtuId))
649         {
650             return false;
651         }
652         if (this.size > 0)
653         {
654             if (this.t[0] != other.t[0])
655             {
656                 return false;
657             }
658         }
659         return true;
660     }
661 
662     /** {@inheritDoc} */
663     @Override
664     public String toString()
665     {
666         if (this.size > 0)
667         {
668             return "Trajectory [size=" + this.size + ", x={" + this.x[0] + "..." + this.x[this.size - 1] + "}, t={" + this.t[0]
669                     + "..." + this.t[this.size - 1] + "}, metaData=" + this.metaData + ", gtuId=" + this.gtuId + "]";
670         }
671         return "Trajectory [size=" + this.size + ", x={}, t={}, metaData=" + this.metaData + ", gtuId=" + this.gtuId + "]";
672     }
673 
674     /**
675      * <p>
676      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
677      * <br>
678      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
679      * <p>
680      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 12 okt. 2016 <br>
681      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
682      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
683      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
684      */
685     private class Boundaries
686     {
687         /** Rounded-down from-index. */
688         @SuppressWarnings("checkstyle:visibilitymodifier")
689         public final int from;
690 
691         /** Fraction of to-index. */
692         @SuppressWarnings("checkstyle:visibilitymodifier")
693         public final double fFrom;
694 
695         /** Rounded-down to-index. */
696         @SuppressWarnings("checkstyle:visibilitymodifier")
697         public final int to;
698 
699         /** Fraction of to-index. */
700         @SuppressWarnings("checkstyle:visibilitymodifier")
701         public final double fTo;
702 
703         /**
704          * @param from from index, rounded down
705          * @param fFrom from index, fraction
706          * @param to to index, rounded down
707          * @param fTo to index, fraction
708          */
709         Boundaries(final int from, final double fFrom, final int to, final double fTo)
710         {
711             Throw.when(from < 0 || from > Trajectory.this.size() - 1, IllegalArgumentException.class,
712                     "Argument from (%d) is out of bounds.", from);
713             Throw.when(fFrom < 0 || fFrom > 1, IllegalArgumentException.class, "Argument fFrom (%f) is out of bounds.", fFrom);
714             Throw.when(from == Trajectory.this.size() && fFrom > 0, IllegalArgumentException.class,
715                     "Arguments from (%d) and fFrom (%f) are out of bounds.", from, fFrom);
716             Throw.when(to < 0 || to >= Trajectory.this.size(), IllegalArgumentException.class,
717                     "Argument to (%d) is out of bounds.", to);
718             Throw.when(fTo < 0 || fTo > 1, IllegalArgumentException.class, "Argument fTo (%f) is out of bounds.", fTo);
719             Throw.when(to == Trajectory.this.size() && fTo > 0, IllegalArgumentException.class,
720                     "Arguments to (%d) and fTo (%f) are out of bounds.", to, fTo);
721             this.from = from;
722             this.fFrom = fFrom;
723             this.to = to;
724             this.fTo = fTo;
725         }
726 
727         /**
728          * @param boundaries boundaries
729          * @return intersection of both boundaries
730          */
731         public Boundaries intersect(final Boundaries boundaries)
732         {
733             int newFrom;
734             double newFFrom;
735             if (this.from > boundaries.from || this.from == boundaries.from && this.fFrom > boundaries.fFrom)
736             {
737                 newFrom = this.from;
738                 newFFrom = this.fFrom;
739             }
740             else
741             {
742                 newFrom = boundaries.from;
743                 newFFrom = boundaries.fFrom;
744             }
745             int newTo;
746             double newFTo;
747             if (this.to < boundaries.to || this.to == boundaries.to && this.fTo < boundaries.fTo)
748             {
749                 newTo = this.to;
750                 newFTo = this.fTo;
751             }
752             else
753             {
754                 newTo = boundaries.to;
755                 newFTo = boundaries.fTo;
756             }
757             return new Boundaries(newFrom, newFFrom, newTo, newFTo);
758         }
759 
760     }
761 
762 }