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