View Javadoc
1   package org.opentrafficsim.core.gtu.plan.operational;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.Iterator;
7   import java.util.List;
8   
9   import org.djunits.unit.AccelerationUnit;
10  import org.djunits.unit.DurationUnit;
11  import org.djunits.unit.LengthUnit;
12  import org.djunits.unit.SpeedUnit;
13  import org.djunits.unit.TimeUnit;
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.djutils.exceptions.Throw;
20  import org.opentrafficsim.core.geometry.OTSGeometryException;
21  import org.opentrafficsim.core.geometry.OTSLine3D;
22  import org.opentrafficsim.core.geometry.OTSPoint3D;
23  import org.opentrafficsim.core.gtu.GTU;
24  import org.opentrafficsim.core.gtu.RelativePosition;
25  import org.opentrafficsim.core.math.Solver;
26  
27  import nl.tudelft.simulation.dsol.logger.SimLogger;
28  import nl.tudelft.simulation.language.d3.DirectedPoint;
29  
30  /**
31   * An Operational plan describes a path through the world with a speed profile that a GTU intends to follow. The OperationalPlan
32   * can be updated or replaced at any time (including before it has been totally executed), for which a tactical planner is
33   * responsible. The operational plan is implemented using segments of the movement (time, location, speed, acceleration) that
34   * the GTU will use to plan its location and movement. Within an OperationalPlan the GTU cannot reverse direction along the path
35   * of movement. This ensures that the timeAtDistance method will never have to select among several valid solutions.
36   * <p>
37   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
38   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
39   * </p>
40   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
41   * initial version Nov 14, 2015 <br>
42   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
43   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
44   */
45  public class OperationalPlan implements Serializable
46  {
47      /** */
48      private static final long serialVersionUID = 20151114L;
49  
50      /** The path to follow from a certain time till a certain time. */
51      private final OTSLine3D path;
52  
53      /** The absolute start time when we start executing the path. */
54      private final Time startTime;
55  
56      /** The GTU speed when we start executing the path. */
57      private final Speed startSpeed;
58  
59      /** The segments that make up the path with an acceleration, constant speed or deceleration profile. */
60      private final List<OperationalPlan.Segment> operationalPlanSegmentList;
61  
62      /** The duration of executing the entire operational plan. */
63      private final Duration totalDuration;
64  
65      /** The length of the entire operational plan. */
66      private final Length totalLength;
67  
68      /** The speed at the end of the operational plan. */
69      private final Speed endSpeed;
70  
71      /** Is this operational plan a wait plan? */
72      private final boolean waitPlan;
73  
74      /** GTU for debugging purposes. */
75      private final GTU gtu;
76  
77      /**
78       * An array of relative start times of each segment, expressed in the SI unit, where the last time is the overall ending
79       * time of the operational plan.
80       */
81      private final double[] segmentStartTimesRelSI;
82  
83      /**
84       * The maximum difference in the length of the path and the calculated driven distance implied by the segment list. The same
85       * constant is also used as a maximum between speeds of segments that should align in terms of speed.
86       */
87      static final double MAX_DELTA_SI = 1E-6;
88  
89      /** The drifting speed. Speeds under this value will be cropped to zero. */
90      public static final double DRIFTING_SPEED_SI = 1E-3;
91  
92      /**
93       * Construct an operational plan.
94       * @param gtu GTU; the GTU for debugging purposes
95       * @param path OTSLine3D; the path to follow from a certain time till a certain time. The path should have &lt;i&gt;at
96       *            least&lt;/i&gt; the length
97       * @param startTime Time; the absolute start time when we start executing the path
98       * @param startSpeed Speed; the GTU speed when we start executing the path
99       * @param operationalPlanSegmentList List&lt;Segment&gt;; the segments that make up the path with an acceleration, constant
100      *            speed or deceleration profile
101      * @throws OperationalPlanException when the path is too short for the operation
102      */
103     public OperationalPlan(final GTU gtu, final OTSLine3D path, final Time startTime, final Speed startSpeed,
104             final List<Segment> operationalPlanSegmentList) throws OperationalPlanException
105     {
106 
107         this.waitPlan = false;
108         this.gtu = gtu;
109         this.startTime = startTime;
110         this.startSpeed = startSpeed;
111         this.operationalPlanSegmentList = operationalPlanSegmentList;
112         this.segmentStartTimesRelSI = new double[this.operationalPlanSegmentList.size() + 1];
113 
114         // check the driven distance of the segments
115         Speed v0 = this.startSpeed;
116         double distanceSI = 0.0;
117         double durationSI = 0.0;
118         for (int i = 0; i < this.operationalPlanSegmentList.size(); i++)
119         {
120             Segment segment = this.operationalPlanSegmentList.get(i);
121             if (Math.abs(v0.si) < DRIFTING_SPEED_SI && segment.accelerationSI(0.0) == 0.0)
122             {
123                 v0 = Speed.ZERO;
124             }
125             segment.setV0(v0);
126             this.segmentStartTimesRelSI[i] = durationSI;
127             distanceSI += segment.distanceSI();
128             v0 = segment.endSpeed();
129             durationSI += segment.getDuration().si;
130         }
131         this.segmentStartTimesRelSI[this.segmentStartTimesRelSI.length - 1] = durationSI;
132         try
133         {
134             this.path = path.extract(0.0, Math.min(distanceSI, path.getLengthSI()));
135         }
136         catch (OTSGeometryException exception)
137         {
138             throw new OperationalPlanException(exception);
139         }
140         this.totalDuration = new Duration(durationSI, DurationUnit.SI);
141         this.totalLength = new Length(distanceSI, LengthUnit.SI);
142         this.endSpeed = v0;
143 
144         // double pathDistanceDeviation = Math.abs(this.totalLength.si - this.path.getLengthSI()) / this.totalLength.si;
145         // if (pathDistanceDeviation < -0.01 || pathDistanceDeviation > 0.01)
146         // {
147         // System.err.println("path length and driven distance deviate more than 1% for operationalPlan: " + this);
148         // }
149     }
150 
151     /**
152      * Build a plan where the GTU will wait for a certain time.
153      * @param gtu GTU; the GTU for debugging purposes
154      * @param waitPoint DirectedPoint; the point at which the GTU will wait
155      * @param startTime Time; the current time or a time in the future when the plan should start
156      * @param duration Duration; the waiting time
157      * @throws OperationalPlanException when construction of a waiting path fails
158      */
159     public OperationalPlan(final GTU gtu, final DirectedPoint waitPoint, final Time startTime, final Duration duration)
160             throws OperationalPlanException
161     {
162         this.waitPlan = true;
163         this.gtu = gtu;
164         this.startTime = startTime;
165         this.startSpeed = Speed.ZERO;
166         this.endSpeed = Speed.ZERO;
167         this.totalDuration = duration;
168         this.totalLength = Length.ZERO;
169 
170         // make a path
171         OTSPoint3D p2 = new OTSPoint3D(waitPoint.x + Math.cos(waitPoint.getRotZ()), waitPoint.y + Math.sin(waitPoint.getRotZ()),
172                 waitPoint.z);
173         try
174         {
175             this.path = new OTSLine3D(new OTSPoint3D(waitPoint), p2);
176         }
177         catch (OTSGeometryException exception)
178         {
179             throw new OperationalPlanException(exception);
180         }
181 
182         this.operationalPlanSegmentList = new ArrayList<>();
183         Segment segment = new SpeedSegment(duration);
184         segment.setV0(Speed.ZERO);
185         this.operationalPlanSegmentList.add(segment);
186         this.segmentStartTimesRelSI = new double[2];
187         this.segmentStartTimesRelSI[0] = 0.0;
188         this.segmentStartTimesRelSI[1] = duration.si;
189     }
190 
191     /**
192      * Return the path that will be traveled. If the plan is a wait plan, the start point of the path is good; the end point of
193      * the path is bogus (should only be used to determine the orientation of the GTU).
194      * @return the path
195      */
196     public final OTSLine3D getPath()
197     {
198         return this.path;
199     }
200 
201     /**
202      * Return the (absolute) start time of the operational plan.
203      * @return startTime
204      */
205     public final Time getStartTime()
206     {
207         return this.startTime;
208     }
209 
210     /**
211      * Return the start speed of the entire plan.
212      * @return startSpeed
213      */
214     public final Speed getStartSpeed()
215     {
216         return this.startSpeed;
217     }
218 
219     /**
220      * @return the end speed when completing the entire plan.
221      */
222     public final Speed getEndSpeed()
223     {
224         return this.endSpeed;
225     }
226 
227     /**
228      * Return the segments (parts with constant speed, acceleration or deceleration) of the operational plan. <br>
229      * The caller MUST NOT MODIFY the returned object.
230      * @return operationalPlanSegmentList
231      */
232     public final List<OperationalPlan.Segment> getOperationalPlanSegmentList()
233     {
234         return this.operationalPlanSegmentList;
235     }
236 
237     /**
238      * Return the time it will take to complete the entire operational plan.
239      * @return the time it will take to complete the entire operational plan
240      */
241     public final Duration getTotalDuration()
242     {
243         return this.totalDuration;
244     }
245 
246     /**
247      * Return the distance the entire operational plan will cover.
248      * @return the distance of the entire operational plan
249      */
250     public final Length getTotalLength()
251     {
252         return this.totalLength;
253     }
254 
255     /**
256      * Return the time it will take to complete the entire operational plan.
257      * @return the time it will take to complete the entire operational plan
258      */
259     public final Time getEndTime()
260     {
261         return this.startTime.plus(this.totalDuration);
262     }
263 
264     /**
265      * Provide the end location of this operational plan as a DirectedPoint.
266      * @return the end location
267      */
268     public final DirectedPoint getEndLocation()
269     {
270         try
271         {
272             if (this.waitPlan)
273             {
274                 return this.path.getLocationFraction(0.0); // no move...
275             }
276             else
277             {
278                 return this.path.getLocationFraction(1.0);
279             }
280         }
281         catch (OTSGeometryException exception)
282         {
283             // should not happen -- only for fractions less than 0.0 or larger than 1.0.
284             throw new RuntimeException(exception);
285         }
286     }
287 
288     /**
289      * Store a Segment and the progress within that segment in one Object.
290      * <p>
291      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. <br>
292      * All rights reserved. <br>
293      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
294      * <p>
295      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Dec 16, 2015 <br>
296      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
297      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
298      */
299     private class SegmentProgress implements Serializable
300     {
301         /** */
302         private static final long serialVersionUID = 20160000L;
303 
304         /** Active Segment. */
305         private final Segment segment;
306 
307         /** Start time of the Segment. */
308         private final Time segmentStartTime;
309 
310         /** Position on the path of the plan. */
311         private final Length segmentStartPosition;
312 
313         /**
314          * Construct a new SegmentProgress object.
315          * @param segment Segment; the Segment
316          * @param segmentStartTime Time; the start time of the Segment
317          * @param segmentStartPosition Length; the position of the start of the segment on the path of the OperationalPlan
318          */
319         SegmentProgress(final Segment segment, final Time segmentStartTime, final Length segmentStartPosition)
320         {
321             this.segment = segment;
322             this.segmentStartTime = segmentStartTime;
323             this.segmentStartPosition = segmentStartPosition;
324         }
325 
326         /**
327          * Retrieve the Segment.
328          * @return Segment
329          */
330         public final Segment getSegment()
331         {
332             return this.segment;
333         }
334 
335         /**
336          * Retrieve the start time of the Segment.
337          * @return Time; the start time of the Segment
338          */
339         public final Time getSegmentStartTime()
340         {
341             return this.segmentStartTime;
342         }
343 
344         /**
345          * Retrieve the fractionalPosition at the start of the Segment.
346          * @return double; the fractional position at the start of the Segment
347          */
348         public final Length getSegmentStartPosition()
349         {
350             return this.segmentStartPosition;
351         }
352 
353         /** {@inheritDoc} */
354         @Override
355         public final String toString()
356         {
357             return String.format("SegmentProgress segment=%s startpos.rel=%s starttime.abs=%s", this.segment,
358                     this.segmentStartPosition, this.segmentStartTime);
359         }
360     }
361 
362     /**
363      * Find the Segment and the progress within that Segment at a specified time.
364      * @param time Time; the time
365      * @return SegmentProgress; the Segment and progress within that segment, or null when no Segment applies to the specified
366      *         time
367      * @throws OperationalPlanException when SegmentProgress cannot be determined
368      */
369     private SegmentProgress getSegmentProgress(final Time time) throws OperationalPlanException
370     {
371         if (time.lt(this.startTime))
372         {
373             throw new OperationalPlanException(
374                     this.gtu + ", t = " + time + "SegmentProgress cannot be determined for time before startTime "
375                             + getStartTime() + " of this OperationalPlan");
376         }
377         double cumulativeDistance = 0;
378         for (int i = 0; i < this.segmentStartTimesRelSI.length - 1; i++)
379         {
380             if (this.startTime.si + this.segmentStartTimesRelSI[i + 1] >= time.si)
381             {
382                 return new SegmentProgress(this.operationalPlanSegmentList.get(i),
383                         new Time(this.startTime.si + this.segmentStartTimesRelSI[i], TimeUnit.BASE),
384                         new Length(cumulativeDistance, LengthUnit.SI));
385             }
386             cumulativeDistance += this.operationalPlanSegmentList.get(i).distanceSI();
387         }
388         throw new OperationalPlanException(this.gtu + ", t = " + time
389                 + " SegmentProgress cannot be determined for time after endTime " + getEndTime() + " of this OperationalPlan");
390     }
391 
392     /**
393      * Return the time when the GTU will reach the given distance.
394      * @param distance Length; the distance to calculate the time for
395      * @return the time it will take to have traveled the given distance
396      */
397     public final Time timeAtDistance(final Length distance)
398     {
399         Throw.when(getTotalLength().lt(distance), RuntimeException.class, "Requesting %s from a plan with length %s", distance,
400                 getTotalLength());
401         double remainingDistanceSI = distance.si;
402         double timeAtStartOfSegment = this.startTime.si;
403         Iterator<Segment> it = this.operationalPlanSegmentList.iterator();
404         while (it.hasNext() && remainingDistanceSI >= 0.0)
405         {
406             Segment segment = it.next();
407             double distanceOfSegment = segment.distanceSI();
408             if (distanceOfSegment > remainingDistanceSI)
409             {
410                 return new Time(timeAtStartOfSegment + segment.timeAtDistance(Length.createSI(remainingDistanceSI)).si,
411                         TimeUnit.BASE);
412             }
413             remainingDistanceSI -= distanceOfSegment;
414             timeAtStartOfSegment += segment.getDurationSI();
415         }
416         return new Time(Double.NaN, TimeUnit.BASE);
417     }
418 
419     /**
420      * Calculate the location at the given time.
421      * @param time Time; the absolute time to look for a location
422      * @return the location at the given time.
423      * @throws OperationalPlanException when the time is after the validity of the operational plan
424      */
425     public final DirectedPoint getLocation(final Time time) throws OperationalPlanException
426     {
427         SegmentProgress sp = getSegmentProgress(time);
428         Segment segment = sp.getSegment();
429         Duration deltaT = time.minus(sp.getSegmentStartTime());
430         double distanceTraveledInSegment = segment.distanceSI(deltaT.si);
431         double startDistance = sp.getSegmentStartPosition().si;
432         double fraction = (startDistance + distanceTraveledInSegment) / this.path.getLengthSI();
433         DirectedPoint p = new DirectedPoint();
434         try
435         {
436             p = this.path.getLocationFraction(fraction, 0.01);
437         }
438         catch (OTSGeometryException exception)
439         {
440             SimLogger.always().error("OperationalPlan.getLocation(): " + exception.getMessage());
441             p = this.path.getLocationFractionExtended(fraction);
442         }
443         p.setZ(p.getZ() + 0.001);
444         return p;
445     }
446 
447     /**
448      * Calculate the speed of the GTU after the given duration since the start of the plan.
449      * @param time Duration; the relative time to look for a location
450      * @return the location after the given duration since the start of the plan.
451      * @throws OperationalPlanException when the time is after the validity of the operational plan
452      */
453     public final Speed getSpeed(final Duration time) throws OperationalPlanException
454     {
455         return getSpeed(time.plus(this.startTime));
456     }
457 
458     /**
459      * Calculate the speed of the GTU at the given time.
460      * @param time Time; the absolute time to look for a location
461      * @return the location at the given time.
462      * @throws OperationalPlanException when the time is after the validity of the operational plan
463      */
464     public final Speed getSpeed(final Time time) throws OperationalPlanException
465     {
466         SegmentProgress sp = getSegmentProgress(time);
467         return new Speed(sp.getSegment().speedSI(time.minus(sp.getSegmentStartTime()).si), SpeedUnit.SI);
468     }
469 
470     /**
471      * Calculate the acceleration of the GTU after the given duration since the start of the plan.
472      * @param time Duration; the relative time to look for a location
473      * @return the location after the given duration since the start of the plan.
474      * @throws OperationalPlanException when the time is after the validity of the operational plan
475      */
476     public final Acceleration getAcceleration(final Duration time) throws OperationalPlanException
477     {
478         return getAcceleration(time.plus(this.startTime));
479     }
480 
481     /**
482      * Calculate the acceleration of the GTU at the given time.
483      * @param time Time; the absolute time to look for a location
484      * @return the location at the given time.
485      * @throws OperationalPlanException when the time is after the validity of the operational plan
486      */
487     public final Acceleration getAcceleration(final Time time) throws OperationalPlanException
488     {
489         SegmentProgress sp = getSegmentProgress(time);
490         return new Acceleration(sp.getSegment().accelerationSI(time.minus(sp.getSegmentStartTime()).si), AccelerationUnit.SI);
491     }
492 
493     /**
494      * Calculate the location after the given duration since the start of the plan.
495      * @param time Duration; the relative time to look for a location
496      * @return the location after the given duration since the start of the plan.
497      * @throws OperationalPlanException when the time is after the validity of the operational plan
498      */
499     public final DirectedPoint getLocation(final Duration time) throws OperationalPlanException
500     {
501         double distanceSI = getTraveledDistanceSI(time);
502         return this.path.getLocationExtendedSI(distanceSI);
503     }
504 
505     /**
506      * Calculate the location after the given duration since the start of the plan.
507      * @param time Time; the relative time to look for a location
508      * @param pos RelativePosition; relative position
509      * @return the location after the given duration since the start of the plan.
510      * @throws OperationalPlanException when the time is after the validity of the operational plan
511      */
512     public final DirectedPoint getLocation(final Time time, final RelativePosition pos) throws OperationalPlanException
513     {
514         double distanceSI = getTraveledDistanceSI(time) + pos.getDx().si;
515         return this.path.getLocationExtendedSI(distanceSI);
516     }
517 
518     /**
519      * Calculate the distance traveled as part of this plan after the given duration since the start of the plan. This method
520      * returns the traveled distance as a double in SI units.
521      * @param duration Duration; the relative time to calculate the traveled distance
522      * @return the distance traveled as part of this plan after the given duration since the start of the plan.
523      * @throws OperationalPlanException when the time is after the validity of the operational plan
524      */
525     public final double getTraveledDistanceSI(final Duration duration) throws OperationalPlanException
526     {
527         return getTraveledDistanceSI(this.startTime.plus(duration));
528     }
529 
530     /**
531      * Calculate the distance traveled as part of this plan after the given duration since the start of the plan.
532      * @param duration Duration; the relative time to calculate the traveled distance
533      * @return the distance traveled as part of this plan after the given duration since the start of the plan.
534      * @throws OperationalPlanException when the time is after the validity of the operational plan
535      */
536     public final Length getTraveledDistance(final Duration duration) throws OperationalPlanException
537     {
538         return new Length(getTraveledDistanceSI(duration), LengthUnit.SI);
539     }
540 
541     /**
542      * Calculate the distance traveled as part of this plan at the given absolute time. This method returns the traveled
543      * distance as a double in SI units.
544      * @param time Time; the absolute time to calculate the traveled distance for as part of this plan
545      * @return the distance traveled as part of this plan at the given time
546      * @throws OperationalPlanException when the time is after the validity of the operational plan
547      */
548     public final double getTraveledDistanceSI(final Time time) throws OperationalPlanException
549     {
550         Throw.when(time.lt(this.getStartTime()), OperationalPlanException.class,
551                 "getTravelDistance exception: requested traveled distance before start of plan");
552         Throw.when(time.si > this.getEndTime().si + 1e-6, OperationalPlanException.class,
553                 "getTravelDistance exception: requested traveled distance beyond end of plan");
554         if (this.operationalPlanSegmentList.size() == 1)
555         {
556             return this.operationalPlanSegmentList.get(0).distanceSI(time.si - this.startTime.si);
557         }
558         SegmentProgress sp = getSegmentProgress(time);
559         return sp.getSegmentStartPosition().si + sp.getSegment().distanceSI(time.minus(sp.getSegmentStartTime()).si);
560     }
561 
562     /**
563      * Calculates when the GTU will be at the given point. The point does not need to be at the traveled path, as the point is
564      * projected to the path at 90 degrees. The point may for instance be the end of a lane, which is crossed by a GTU possibly
565      * during a lane change.
566      * @param point DirectedPoint; point with angle, which will be projected to the path at 90 degrees
567      * @param upstream boolean; true if the point is upstream of the path
568      * @return Time; time at point
569      */
570     public final Time timeAtPoint(final DirectedPoint point, final boolean upstream)
571     {
572         OTSPoint3D p1 = new OTSPoint3D(point);
573         // point at 90 degrees
574         OTSPoint3D p2 = new OTSPoint3D(point.x - Math.sin(point.getRotZ()), point.y + Math.cos(point.getRotZ()), point.z);
575         double traveledDistanceAlongPath = 0.0;
576         try
577         {
578             if (upstream)
579             {
580                 OTSPoint3D p = OTSPoint3D.intersectionOfLines(this.path.get(0), this.path.get(1), p1, p2);
581                 double dist = traveledDistanceAlongPath - this.path.get(0).distance(p).si;
582                 dist = dist >= 0.0 ? dist : 0.0; // negative in case of a gap
583                 return timeAtDistance(Length.createSI(dist));
584             }
585             for (int i = 0; i < this.path.size() - 1; i++)
586             {
587                 OTSPoint3D prevPoint = this.path.get(i);
588                 OTSPoint3D nextPoint = this.path.get(i + 1);
589                 OTSPoint3D p = OTSPoint3D.intersectionOfLines(prevPoint, nextPoint, p1, p2);
590                 if (p == null)
591                 {
592                     // point too close, check next section
593                     continue;
594                 }
595                 boolean onSegment =
596                         prevPoint.distanceSI(nextPoint) + 2e-5 > Math.max(prevPoint.distanceSI(p), nextPoint.distanceSI(p));
597                 // (prevPoint.x - p.x) * (nextPoint.x - p.x) <= 2e-5/* PK 1e-6 is too small */
598                 // && (prevPoint.y - p.y) * (nextPoint.y - p.y) <= 2e-5/* PK 1e-6 is too small */;
599                 // if (i > 64)
600                 // {
601                 // System.err.println(String.format("i=%d, prevPoint=%s, nextPoint=%s, intersection=%s, onSegment=%s", i,
602                 // prevPoint, nextPoint, p, onSegment));
603                 // }
604                 if (p != null // on segment, or last segment
605                         && (i == this.path.size() - 2 || onSegment))
606                 {
607                     // point is on the line
608                     traveledDistanceAlongPath += this.path.get(i).distance(p).si;
609                     if (traveledDistanceAlongPath > this.path.getLengthSI())
610                     {
611                         return Time.createSI(getEndTime().si - 1e-9); // -1e-9 prevents that next move() reschedules enter
612                     }
613                     return timeAtDistance(Length.createSI(traveledDistanceAlongPath));
614                 }
615                 else
616                 {
617                     traveledDistanceAlongPath += this.path.get(i).distance(this.path.get(i + 1)).si;
618                 }
619             }
620         }
621         catch (OTSGeometryException exception)
622         {
623             throw new RuntimeException("Index out of bounds on projection of point to path of operational plan", exception);
624         }
625         SimLogger.always().error("timeAtPoint failed");
626         return null;
627     }
628 
629     /**
630      * Calculate the distance traveled as part of this plan at the given absolute time.
631      * @param time Time; the absolute time to calculate the traveled distance for as part of this plan
632      * @return the distance traveled as part of this plan at the given time
633      * @throws OperationalPlanException when the time is after the validity of the operational plan
634      */
635     public final Length getTraveledDistance(final Time time) throws OperationalPlanException
636     {
637         return new Length(getTraveledDistanceSI(time.minus(this.startTime)), LengthUnit.SI);
638     }
639 
640     /** {@inheritDoc} */
641     @SuppressWarnings("checkstyle:designforextension")
642     @Override
643     public int hashCode()
644     {
645         final int prime = 31;
646         int result = 1;
647         result = prime * result + ((this.operationalPlanSegmentList == null) ? 0 : this.operationalPlanSegmentList.hashCode());
648         result = prime * result + ((this.path == null) ? 0 : this.path.hashCode());
649         result = prime * result + ((this.startSpeed == null) ? 0 : this.startSpeed.hashCode());
650         result = prime * result + ((this.startTime == null) ? 0 : this.startTime.hashCode());
651         return result;
652     }
653 
654     /** {@inheritDoc} */
655     @SuppressWarnings({"checkstyle:needbraces", "checkstyle:designforextension"})
656     @Override
657     public boolean equals(final Object obj)
658     {
659         if (this == obj)
660             return true;
661         if (obj == null)
662             return false;
663         if (getClass() != obj.getClass())
664             return false;
665         OperationalPlan other = (OperationalPlan) obj;
666         if (this.operationalPlanSegmentList == null)
667         {
668             if (other.operationalPlanSegmentList != null)
669                 return false;
670         }
671         else if (!this.operationalPlanSegmentList.equals(other.operationalPlanSegmentList))
672             return false;
673         if (this.path == null)
674         {
675             if (other.path != null)
676                 return false;
677         }
678         else if (!this.path.equals(other.path))
679             return false;
680         if (this.startSpeed == null)
681         {
682             if (other.startSpeed != null)
683                 return false;
684         }
685         else if (!this.startSpeed.equals(other.startSpeed))
686             return false;
687         if (this.startTime == null)
688         {
689             if (other.startTime != null)
690                 return false;
691         }
692         else if (!this.startTime.equals(other.startTime))
693             return false;
694         return true;
695     }
696 
697     /** {@inheritDoc} */
698     @SuppressWarnings("checkstyle:designforextension")
699     @Override
700     public String toString()
701     {
702         return "OperationalPlan [path=" + this.path + ", startTime=" + this.startTime + ", startSpeed=" + this.startSpeed
703                 + ", operationalPlanSegmentList=" + this.operationalPlanSegmentList + ", totalDuration=" + this.totalDuration
704                 + ", segmentStartTimesSI=" + Arrays.toString(this.segmentStartTimesRelSI) + ", endSpeed = " + this.endSpeed
705                 + "]";
706     }
707 
708     /****************************************************************************************************************/
709     /******************************************** SEGMENT DEFINITIONS ***********************************************/
710     /****************************************************************************************************************/
711 
712     /**
713      * The segment of an operational plan contains a part of the speed profile of a movement in which some of the variables
714      * determining movement (speed, acceleration) are constant.
715      * <p>
716      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. <br>
717      * All rights reserved. <br>
718      * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
719      * </p>
720      * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
721      * initial version Nov 14, 2015 <br>
722      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
723      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
724      */
725     public abstract static class Segment implements Serializable
726     {
727         /** */
728         private static final long serialVersionUID = 20151114L;
729 
730         /** The duration of the acceleration or speed for this segment. */
731         @SuppressWarnings("checkstyle:visibilitymodifier")
732         protected final Duration duration;
733 
734         /** The initial speed for this segment. */
735         @SuppressWarnings("checkstyle:visibilitymodifier")
736         protected Speed v0;
737 
738         /**
739          * @param duration Duration; the duration of the acceleration or speed for this segment
740          */
741         public Segment(final Duration duration)
742         {
743             super();
744             this.duration = duration;
745         }
746 
747         /**
748          * @param v0 the initial speed of this segment; called from the Operational Plan constructor.
749          */
750         final void setV0(final Speed v0)
751         {
752             this.v0 = v0;
753         }
754 
755         /**
756          * @return duration the duration of the acceleration or speed for this segment
757          */
758         public final Duration getDuration()
759         {
760             return this.duration;
761         }
762 
763         /**
764          * @return duration the duration of the acceleration or speed for this segment
765          */
766         public final double getDurationSI()
767         {
768             return this.duration.si;
769         }
770 
771         /**
772          * Calculate the distance covered by a GTU in this segment.
773          * @return distance covered
774          */
775         final double distanceSI()
776         {
777             return distanceSI(getDuration().si);
778         }
779 
780         /**
781          * Calculate the distance covered by a GTU in this segment after relative time t.
782          * @param t double; the relative time since starting this segment for which to calculate the distance covered
783          * @return distance covered
784          */
785         abstract double distanceSI(double t);
786 
787         /**
788          * Calculate the speed of a GTU in this segment after relative time t.
789          * @param t double; the relative time since starting this segment for which to calculate the speed
790          * @return speed at relative time t
791          */
792         abstract double speedSI(double t);
793 
794         /**
795          * Calculate the acceleration of a GTU in this segment after relative time t.
796          * @param t double; the relative time since starting this segment for which to calculate the acceleration
797          * @return acceleration at relative time t
798          */
799         abstract double accelerationSI(double t);
800 
801         /**
802          * Calculate the end speed for this segment.
803          * @return speed at end of the segment
804          */
805         abstract Speed endSpeed();
806 
807         /**
808          * Calculate the time it takes for the GTU to travel from the start of this Segment to the specified distance within
809          * this Segment.
810          * @param distance Length; the distance for which the travel time has to be calculated
811          * @return the time at distance
812          */
813         abstract Duration timeAtDistance(final Length distance);
814 
815         /** {@inheritDoc} */
816         @SuppressWarnings("checkstyle:designforextension")
817         @Override
818         public int hashCode()
819         {
820             final int prime = 31;
821             int result = 1;
822             result = prime * result + ((this.duration == null) ? 0 : this.duration.hashCode());
823             result = prime * result + ((this.v0 == null) ? 0 : this.v0.hashCode());
824             return result;
825         }
826 
827         /** {@inheritDoc} */
828         @SuppressWarnings({"checkstyle:needbraces", "checkstyle:designforextension"})
829         @Override
830         public boolean equals(final Object obj)
831         {
832             if (this == obj)
833                 return true;
834             if (obj == null)
835                 return false;
836             if (getClass() != obj.getClass())
837                 return false;
838             Segment other = (Segment) obj;
839             if (this.duration == null)
840             {
841                 if (other.duration != null)
842                     return false;
843             }
844             else if (!this.duration.equals(other.duration))
845                 return false;
846             if (this.v0 == null)
847             {
848                 if (other.v0 != null)
849                     return false;
850             }
851             else if (!this.v0.equals(other.v0))
852                 return false;
853             return true;
854         }
855     }
856 
857     /**
858      * The segment of an operational plan in which the acceleration is constant.
859      * <p>
860      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. <br>
861      * All rights reserved. <br>
862      * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
863      * </p>
864      * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
865      * initial version Nov 14, 2015 <br>
866      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
867      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
868      */
869     public static class AccelerationSegment extends Segment
870     {
871         /** */
872         private static final long serialVersionUID = 20151114L;
873 
874         /** The acceleration for the given duration. */
875         private final Acceleration acceleration;
876 
877         /**
878          * @param duration Duration; the duration of the constant acceleration for this segment
879          * @param acceleration Acceleration; the acceleration for the given duration
880          */
881         public AccelerationSegment(final Duration duration, final Acceleration acceleration)
882         {
883             super(duration);
884             this.acceleration = acceleration;
885         }
886 
887         /** {@inheritDoc} */
888         @Override
889         final double distanceSI(final double t)
890         {
891             return this.v0.si * t + 0.5 * this.acceleration.si * t * t;
892         }
893 
894         /** {@inheritDoc} */
895         @Override
896         final double accelerationSI(final double t)
897         {
898             return this.acceleration.si;
899         }
900 
901         /** {@inheritDoc} */
902         @Override
903         final double speedSI(final double t)
904         {
905             return this.v0.si + this.acceleration.si * t;
906         }
907 
908         /** {@inheritDoc} */
909         @Override
910         final Speed endSpeed()
911         {
912             return this.v0.plus(this.acceleration.multiplyBy(getDuration()));
913         }
914 
915         /** {@inheritDoc} */
916         @Override
917         public final int hashCode()
918         {
919             final int prime = 31;
920             int result = super.hashCode();
921             result = prime * result + ((this.acceleration == null) ? 0 : this.acceleration.hashCode());
922             return result;
923         }
924 
925         /** {@inheritDoc} */
926         @Override
927         @SuppressWarnings("checkstyle:needbraces")
928         public final boolean equals(final Object obj)
929         {
930             if (this == obj)
931                 return true;
932             if (!super.equals(obj))
933                 return false;
934             if (getClass() != obj.getClass())
935                 return false;
936             AccelerationSegment other = (AccelerationSegment) obj;
937             if (this.acceleration == null)
938             {
939                 if (other.acceleration != null)
940                     return false;
941             }
942             else if (!this.acceleration.equals(other.acceleration))
943                 return false;
944             return true;
945         }
946 
947         /** {@inheritDoc} */
948         @Override
949         public final Duration timeAtDistance(final Length distance)
950         {
951             double[] solutions = Solver.solve(this.acceleration.si / 2, this.v0.si, -distance.si);
952             // Find the solution that occurs within our duration (there should be only one).
953             for (double solution : solutions)
954             {
955                 if (solution >= 0 && solution <= this.duration.si)
956                 {
957                     return new Duration(solution, DurationUnit.SI);
958                 }
959             }
960             SimLogger.always().error("AccelerationSegment " + this + " timeAtDistance( " + distance + ") failed");
961             return null; // No valid solution
962         }
963 
964         /** {@inheritDoc} */
965         @Override
966         public final String toString()
967         {
968             return "AccelerationSegment [t=" + this.duration + ", v0=" + this.v0 + ", a=" + this.acceleration + "]";
969         }
970 
971     }
972 
973     /**
974      * The segment of an operational plan in which the speed is constant.
975      * <p>
976      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. <br>
977      * All rights reserved. <br>
978      * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
979      * </p>
980      * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
981      * initial version Nov 14, 2015 <br>
982      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
983      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
984      */
985     public static class SpeedSegment extends Segment
986     {
987         /** */
988         private static final long serialVersionUID = 20151114L;
989 
990         /**
991          * @param duration Duration; the duration of the constant speed for this segment
992          */
993         public SpeedSegment(final Duration duration)
994         {
995             super(duration);
996         }
997 
998         /** {@inheritDoc} */
999         @Override
1000         final double distanceSI(final double t)
1001         {
1002             return this.v0.si * t;
1003         }
1004 
1005         /** {@inheritDoc} */
1006         @Override
1007         final double accelerationSI(final double t)
1008         {
1009             return 0.0;
1010         }
1011 
1012         /** {@inheritDoc} */
1013         @Override
1014         final double speedSI(final double t)
1015         {
1016             return this.v0.si;
1017         }
1018 
1019         /** {@inheritDoc} */
1020         @Override
1021         final Speed endSpeed()
1022         {
1023             return this.v0;
1024         }
1025 
1026         /**
1027          * @return speed
1028          */
1029         public final Speed getSpeed()
1030         {
1031             return this.v0;
1032         }
1033 
1034         /** {@inheritDoc} */
1035         @Override
1036         public final Duration timeAtDistance(final Length distance)
1037         {
1038             double[] solution = Solver.solve(this.v0.si, -distance.si);
1039             if (solution.length > 0 && solution[0] >= 0 && solution[0] <= getDurationSI())
1040             {
1041                 return new Duration(solution[0], DurationUnit.SI);
1042             }
1043             SimLogger.always().error("SpeedSegment " + this + " timeAtDistance( " + distance + ") failed");
1044             return null; // No valid solution
1045         }
1046 
1047         /** {@inheritDoc} */
1048         @Override
1049         public final String toString()
1050         {
1051             return "SpeedSegment [t=" + this.duration + ", v0=" + this.v0 + "]";
1052         }
1053 
1054     }
1055 
1056 }