View Javadoc
1   package org.opentrafficsim.core.gtu;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.LinkedHashMap;
7   import java.util.LinkedHashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import org.djunits.unit.DirectionUnit;
13  import org.djunits.unit.DurationUnit;
14  import org.djunits.unit.PositionUnit;
15  import org.djunits.unit.TimeUnit;
16  import org.djunits.value.vdouble.scalar.Acceleration;
17  import org.djunits.value.vdouble.scalar.Direction;
18  import org.djunits.value.vdouble.scalar.Duration;
19  import org.djunits.value.vdouble.scalar.Length;
20  import org.djunits.value.vdouble.scalar.Speed;
21  import org.djunits.value.vdouble.scalar.Time;
22  import org.djunits.value.vdouble.vector.PositionVector;
23  import org.djutils.draw.point.Point3d;
24  import org.djutils.event.EventType;
25  import org.djutils.event.LocalEventProducer;
26  import org.djutils.exceptions.Throw;
27  import org.djutils.exceptions.Try;
28  import org.djutils.immutablecollections.Immutable;
29  import org.djutils.immutablecollections.ImmutableHashSet;
30  import org.djutils.immutablecollections.ImmutableLinkedHashMap;
31  import org.djutils.immutablecollections.ImmutableMap;
32  import org.djutils.immutablecollections.ImmutableSet;
33  import org.djutils.metadata.MetaData;
34  import org.djutils.metadata.ObjectDescriptor;
35  import org.opentrafficsim.base.HierarchicallyTyped;
36  import org.opentrafficsim.base.Identifiable;
37  import org.opentrafficsim.base.parameters.ParameterException;
38  import org.opentrafficsim.base.parameters.Parameters;
39  import org.opentrafficsim.core.DynamicSpatialObject;
40  import org.opentrafficsim.core.animation.Drawable;
41  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
42  import org.opentrafficsim.core.geometry.Bounds;
43  import org.opentrafficsim.core.geometry.DirectedPoint;
44  import org.opentrafficsim.core.geometry.OtsGeometryException;
45  import org.opentrafficsim.core.geometry.OtsLine3d;
46  import org.opentrafficsim.core.geometry.OtsPoint3d;
47  import org.opentrafficsim.core.geometry.OtsShape;
48  import org.opentrafficsim.core.gtu.RelativePosition.TYPE;
49  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
50  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
51  import org.opentrafficsim.core.gtu.plan.strategical.StrategicalPlanner;
52  import org.opentrafficsim.core.gtu.plan.tactical.TacticalPlanner;
53  import org.opentrafficsim.core.network.NetworkException;
54  import org.opentrafficsim.core.perception.Historical;
55  import org.opentrafficsim.core.perception.HistoricalValue;
56  import org.opentrafficsim.core.perception.HistoryManager;
57  import org.opentrafficsim.core.perception.PerceivableContext;
58  
59  import nl.tudelft.simulation.dsol.SimRuntimeException;
60  import nl.tudelft.simulation.dsol.animation.Locatable;
61  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
62  
63  /**
64   * Implements the basic functionalities of any GTU: the ability to move on 3D-space according to a plan.
65   * <p>
66   * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
67   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
68   * </p>
69   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
70   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
71   */
72  public class Gtu extends LocalEventProducer
73          implements HierarchicallyTyped<GtuType, Gtu>, DynamicSpatialObject, Locatable, Serializable, Identifiable, Drawable
74  {
75      /** */
76      private static final long serialVersionUID = 20140822L;
77  
78      /** The id of the GTU. */
79      private final String id;
80  
81      /** unique number of the GTU. */
82      private final int uniqueNumber;
83  
84      /** the unique number counter. */
85      private static int staticUNIQUENUMBER = 0;
86  
87      /** The type of GTU, e.g. TruckType, CarType, BusType. */
88      private final GtuType gtuType;
89  
90      /** The simulator to schedule activities on. */
91      private final OtsSimulatorInterface simulator;
92  
93      /** Model parameters. */
94      private Parameters parameters;
95  
96      /** The maximum acceleration. */
97      private Acceleration maximumAcceleration;
98  
99      /** The maximum deceleration, stored as a negative number. */
100     private Acceleration maximumDeceleration;
101 
102     /**
103      * The odometer which measures how much distance have we covered between instantiation and the last completed operational
104      * plan. In order to get a complete odometer reading, the progress of the current plan execution has to be added to this
105      * value.
106      */
107     private Historical<Length> odometer;
108 
109     /** The strategical planner that can instantiate tactical planners to determine mid-term decisions. */
110     private final Historical<StrategicalPlanner> strategicalPlanner;
111 
112     /** The tactical planner that can generate an operational plan. */
113     private final Historical<TacticalPlanner<?, ?>> tacticalPlanner;
114 
115     /** The current operational plan, which provides a short-term movement over time. */
116     private final Historical<OperationalPlan> operationalPlan;
117 
118     /** The next move event as scheduled on the simulator, can be used for interrupting the current move. */
119     private SimEvent<Duration> nextMoveEvent;
120 
121     /** The model in which this GTU is registered. */
122     private PerceivableContext perceivableContext;
123 
124     /** Is this GTU destroyed? */
125     private boolean destroyed = false;
126 
127     /** aligned or not. */
128     // TODO: should be indicated with a Parameter
129     public static boolean ALIGNED = true;
130 
131     /** aligned schedule count. */
132     // TODO: can be removed after testing period
133     public static int ALIGN_COUNT = 0;
134 
135     /** Cached speed time. */
136     private double cachedSpeedTime = Double.NaN;
137 
138     /** Cached speed. */
139     private Speed cachedSpeed = null;
140 
141     /** Cached acceleration time. */
142     private double cachedAccelerationTime = Double.NaN;
143 
144     /** Cached acceleration. */
145     private Acceleration cachedAcceleration = null;
146 
147     /** Parent GTU. */
148     private Gtu parent = null;
149 
150     /** Children GTU's. */
151     private Set<Gtu> children = new LinkedHashSet<>();
152 
153     /** Error handler. */
154     private GtuErrorHandler errorHandler = GtuErrorHandler.THROW;
155 
156     /** shape of the Gtu contour. */
157     private OtsShape shape = null;
158 
159     /** Sensing positions. */
160     private final Map<RelativePosition.TYPE, RelativePosition> relativePositions = new LinkedHashMap<>();
161 
162     /** cached front. */
163     private final RelativePosition frontPos;
164 
165     /** cached rear. */
166     private final RelativePosition rearPos;
167 
168     /** contour points. */
169     private final Set<RelativePosition> contourPoints = new LinkedHashSet<>();
170 
171     /** The maximum length of the GTU (parallel with driving direction). */
172     private final Length length;
173 
174     /** The maximum width of the GTU (perpendicular to driving direction). */
175     private final Length width;
176 
177     /** The maximum speed of the GTU (in the driving direction). */
178     private final Speed maximumSpeed;
179     
180     /** Tags of the GTU, these are used for specific use cases of any sort. */
181     private final Map<String, String> tags = new LinkedHashMap<>();
182 
183     /**
184      * @param id String; the id of the GTU
185      * @param gtuType GtuType; the type of GTU, e.g. TruckType, CarType, BusType
186      * @param simulator OtsSimulatorInterface; the simulator to schedule plan changes on
187      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
188      * @param length Length; the maximum length of the GTU (parallel with driving direction)
189      * @param width Length; the maximum width of the GTU (perpendicular to driving direction)
190      * @param maximumSpeed Speed;the maximum speed of the GTU (in the driving direction)
191      * @param front Length; front distance relative to the reference position
192      * @param centerOfGravity Length; distance from the center of gravity to the reference position
193      * @throws GtuException when the preconditions of the constructor are not met
194      */
195     @SuppressWarnings("checkstyle:parameternumber")
196     public Gtu(final String id, final GtuType gtuType, final OtsSimulatorInterface simulator,
197             final PerceivableContext perceivableContext, final Length length, final Length width, final Speed maximumSpeed,
198             final Length front, final Length centerOfGravity) throws GtuException
199     {
200         Throw.when(id == null, GtuException.class, "id is null");
201         Throw.when(gtuType == null, GtuException.class, "gtuType is null");
202         Throw.when(perceivableContext == null, GtuException.class, "perceivableContext is null for GTU with id %s", id);
203         Throw.when(perceivableContext.containsGtuId(id), GtuException.class,
204                 "GTU with id %s already registered in perceivableContext %s", id, perceivableContext.getId());
205         Throw.when(simulator == null, GtuException.class, "simulator is null for GTU with id %s", id);
206 
207         this.length = length;
208         this.width = width;
209         if (null == maximumSpeed)
210         {
211             throw new GtuException("maximumSpeed may not be null");
212         }
213         this.maximumSpeed = maximumSpeed;
214 
215         HistoryManager historyManager = simulator.getReplication().getHistoryManager(simulator);
216         this.id = id;
217         this.uniqueNumber = ++staticUNIQUENUMBER;
218         this.gtuType = gtuType;
219         this.simulator = simulator;
220         this.odometer = new HistoricalValue<>(historyManager, Length.ZERO);
221         this.perceivableContext = perceivableContext;
222         this.perceivableContext.addGTU(this);
223         this.strategicalPlanner = new HistoricalValue<>(historyManager);
224         this.tacticalPlanner = new HistoricalValue<>(historyManager, null);
225         this.operationalPlan = new HistoricalValue<>(historyManager, null);
226 
227         // sensor positions.
228         Length dy2 = width.times(0.5);
229         this.frontPos = new RelativePosition(front, Length.ZERO, Length.ZERO, RelativePosition.FRONT);
230         this.relativePositions.put(RelativePosition.FRONT, this.frontPos);
231         this.rearPos = new RelativePosition(front.minus(length), Length.ZERO, Length.ZERO, RelativePosition.REAR);
232         this.relativePositions.put(RelativePosition.REAR, this.rearPos);
233         this.relativePositions.put(RelativePosition.REFERENCE, RelativePosition.REFERENCE_POSITION);
234         this.relativePositions.put(RelativePosition.CENTER,
235                 new RelativePosition(Length.ZERO, Length.ZERO, Length.ZERO, RelativePosition.CENTER));
236 
237         // Contour positions. For now, a rectangle with the four corners.
238         for (int i = -1; i <= 1; i += 2)
239         {
240             Length x = i < 0 ? front.minus(length) : front;
241             for (int j = -1; j <= 1; j += 2)
242             {
243                 this.contourPoints.add(new RelativePosition(x, dy2.times(j), Length.ZERO, RelativePosition.CONTOUR));
244             }
245         }
246     }
247 
248     /**
249      * Initialize the GTU at a location and speed, and give it a mission to fulfill through the strategical planner.
250      * @param strategicalPlanner StrategicalPlanner; the strategical planner responsible for the overall 'mission' of the GTU,
251      *            usually indicating where it needs to go. It operates by instantiating tactical planners to do the work.
252      * @param initialLocation DirectedPoint; the initial location (and direction) of the GTU
253      * @param initialSpeed Speed; the initial speed of the GTU
254      * @throws SimRuntimeException when scheduling after the first move fails
255      * @throws GtuException when the preconditions of the parameters are not met or when the construction of the original
256      *             waiting path fails
257      */
258     @SuppressWarnings({"checkstyle:hiddenfield", "hiding", "checkstyle:designforextension"})
259     public void init(final StrategicalPlanner strategicalPlanner, final DirectedPoint initialLocation, final Speed initialSpeed)
260             throws SimRuntimeException, GtuException
261     {
262         Throw.when(strategicalPlanner == null, GtuException.class, "strategicalPlanner is null for GTU with id %s", this.id);
263         Throw.whenNull(initialLocation, "Initial location of GTU cannot be null");
264         Throw.when(Double.isNaN(initialLocation.x) || Double.isNaN(initialLocation.y) || Double.isNaN(initialLocation.z),
265                 GtuException.class, "initialLocation %s invalid for GTU with id %s", initialLocation, this.id);
266         Throw.when(initialSpeed == null, GtuException.class, "initialSpeed is null for GTU with id %s", this.id);
267         Throw.when(!getId().equals(strategicalPlanner.getGtu().getId()), GtuException.class,
268                 "GTU %s is initialized with a strategical planner for GTU %s", getId(), strategicalPlanner.getGtu().getId());
269 
270         this.strategicalPlanner.set(strategicalPlanner);
271         this.tacticalPlanner.set(strategicalPlanner.getTacticalPlanner());
272 
273         try
274         {
275             move(initialLocation);
276         }
277         catch (OperationalPlanException | NetworkException | ParameterException exception)
278         {
279             throw new GtuException("Failed to create OperationalPlan for GTU " + this.id, exception);
280         }
281     }
282 
283     /** @return the front position of the GTU, relative to its reference point. */
284     public final RelativePosition getFront()
285     {
286         return this.frontPos;
287     }
288 
289     /** @return the rear position of the GTU, relative to its reference point. */
290     public final RelativePosition getRear()
291     {
292         return this.rearPos;
293     }
294 
295     /** @return the center position of the GTU, relative to its reference point. */
296     public final RelativePosition getCenter()
297     {
298         return this.relativePositions.get(RelativePosition.CENTER);
299     }
300 
301     /** @return the positions for this GTU, but not the contour points. */
302     public final ImmutableMap<TYPE, RelativePosition> getRelativePositions()
303     {
304         return new ImmutableLinkedHashMap<>(this.relativePositions, Immutable.WRAP);
305     }
306 
307     /** @return the contour points of the GTU. */
308     public final ImmutableSet<RelativePosition> getContourPoints()
309     {
310         return new ImmutableHashSet<>(this.contourPoints, Immutable.WRAP);
311     }
312 
313     /** @return the maximum length of the GTU (parallel with driving direction). */
314     public final Length getLength()
315     {
316         return this.length;
317     }
318 
319     /** @return the maximum width of the GTU (perpendicular to driving direction). */
320     public final Length getWidth()
321     {
322         return this.width;
323     }
324 
325     /** @return the maximum speed of the GTU, in the direction of movement. */
326     public final Speed getMaximumSpeed()
327     {
328         return this.maximumSpeed;
329     }
330 
331     /** {@inheritDoc} */
332     @Override
333     public final Bounds getBounds()
334     {
335         double dx = 0.5 * getLength().doubleValue();
336         double dy = 0.5 * getWidth().doubleValue();
337         return new Bounds(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
338     }
339 
340     /**
341      * Destructor. Don't forget to call with super.destroy() from any override to avoid memory leaks in the network.
342      */
343     @SuppressWarnings("checkstyle:designforextension")
344     public void destroy()
345     {
346         DirectedPoint location = getLocation();
347         fireTimedEvent(Gtu.DESTROY_EVENT,
348                 new Object[] {getId(), new OtsPoint3d(location).doubleVector(PositionUnit.METER),
349                         new Direction(location.getZ(), DirectionUnit.EAST_RADIAN), getOdometer()},
350                 this.simulator.getSimulatorTime());
351 
352         // cancel the next move
353         if (this.nextMoveEvent != null)
354         {
355             this.simulator.cancelEvent(this.nextMoveEvent);
356             this.nextMoveEvent = null;
357         }
358 
359         this.perceivableContext.removeGTU(this);
360         this.destroyed = true;
361     }
362 
363     /**
364      * Move from the current location according to an operational plan to a location that will bring us nearer to reaching the
365      * location provided by the strategical planner. <br>
366      * This method can be overridden to carry out specific behavior during the execution of the plan (e.g., scheduling of
367      * triggers, entering or leaving lanes, etc.). Please bear in mind that the call to super.move() is essential, and that one
368      * has to take care to handle the situation that the plan gets interrupted.
369      * @param fromLocation DirectedPoint; the last known location (initial location, or end location of the previous operational
370      *            plan)
371      * @return boolean; whether an exception occurred
372      * @throws SimRuntimeException when scheduling of the next move fails
373      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
374      * @throws GtuException when there is a problem with the state of the GTU when planning a path
375      * @throws NetworkException in case of a problem with the network, e.g., a dead end where it is not expected
376      * @throws ParameterException in there is a parameter problem
377      */
378     @SuppressWarnings("checkstyle:designforextension")
379     protected boolean move(final DirectedPoint fromLocation)
380             throws SimRuntimeException, OperationalPlanException, GtuException, NetworkException, ParameterException
381     {
382         try
383         {
384             Time now = this.simulator.getSimulatorAbsTime();
385 
386             // Add the odometer distance from the currently running operational plan.
387             // Because a plan can be interrupted, we explicitly calculate the covered distance till 'now'
388             Length currentOdometer;
389             if (this.operationalPlan.get() != null)
390             {
391                 currentOdometer = this.odometer.get().plus(this.operationalPlan.get().getTraveledDistance(now));
392             }
393             else
394             {
395                 currentOdometer = this.odometer.get();
396             }
397 
398             // Do we have an operational plan?
399             // TODO discuss when a new tactical planner may be needed
400             TacticalPlanner<?, ?> tactPlanner = this.tacticalPlanner.get();
401             if (tactPlanner == null)
402             {
403                 // Tell the strategical planner to provide a tactical planner
404                 tactPlanner = this.strategicalPlanner.get().getTacticalPlanner();
405                 this.tacticalPlanner.set(tactPlanner);
406             }
407             synchronized (this)
408             {
409                 tactPlanner.getPerception().perceive();
410             }
411             OperationalPlan newOperationalPlan = tactPlanner.generateOperationalPlan(now, fromLocation);
412             synchronized (this)
413             {
414                 this.operationalPlan.set(newOperationalPlan);
415                 this.cachedSpeedTime = Double.NaN;
416                 this.cachedAccelerationTime = Double.NaN;
417                 this.odometer.set(currentOdometer);
418             }
419 
420             // TODO allow alignment at different intervals, also different between GTU's within a single simulation
421             if (ALIGNED && newOperationalPlan.getTotalDuration().si == 0.5)
422             {
423                 // schedule the next move at exactly 0.5 seconds on the clock
424                 // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
425                 double tNext = Math.floor(2.0 * now.si + 1.0) / 2.0;
426                 DirectedPoint p = (tNext - now.si < 0.5) ? newOperationalPlan.getEndLocation()
427                         : newOperationalPlan.getLocation(new Duration(tNext - now.si, DurationUnit.SI));
428                 this.nextMoveEvent =
429                         new SimEvent<Duration>(new Duration(tNext - getSimulator().getStartTimeAbs().si, DurationUnit.SI), this,
430                                 "move", new Object[] {p});
431                 ALIGN_COUNT++;
432             }
433             else
434             {
435                 // schedule the next move at the end of the current operational plan
436                 // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
437                 this.nextMoveEvent =
438                         new SimEvent<>(now.plus(newOperationalPlan.getTotalDuration()).minus(getSimulator().getStartTimeAbs()),
439                                 this, "move", new Object[] {newOperationalPlan.getEndLocation()});
440             }
441             this.simulator.scheduleEvent(this.nextMoveEvent);
442             fireTimedEvent(Gtu.MOVE_EVENT,
443                     new Object[] {getId(), new OtsPoint3d(fromLocation).doubleVector(PositionUnit.METER),
444                             new Direction(fromLocation.getZ(), DirectionUnit.EAST_RADIAN), getSpeed(), getAcceleration(),
445                             getOdometer()},
446                     this.simulator.getSimulatorTime());
447 
448             return false;
449         }
450         catch (Exception ex)
451         {
452             try
453             {
454                 this.errorHandler.handle(this, ex);
455             }
456             catch (Exception exception)
457             {
458                 throw new GtuException(exception);
459             }
460             return true;
461         }
462     }
463 
464     /**
465      * Interrupt the move and ask for a new plan. This method can be overridden to carry out the bookkeeping needed when the
466      * current plan gets interrupted.
467      * @throws OperationalPlanException when there was a problem retrieving the location from the running plan
468      * @throws SimRuntimeException when scheduling of the next move fails
469      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
470      * @throws GtuException when there is a problem with the state of the GTU when planning a path
471      * @throws NetworkException in case of a problem with the network, e.g., unreachability of a certain point
472      * @throws ParameterException when there is a problem with a parameter
473      */
474     @SuppressWarnings("checkstyle:designforextension")
475     protected void interruptMove()
476             throws SimRuntimeException, OperationalPlanException, GtuException, NetworkException, ParameterException
477     {
478         this.simulator.cancelEvent(this.nextMoveEvent);
479         move(this.operationalPlan.get().getLocation(this.simulator.getSimulatorTime()));
480     }
481 
482     /** @return the id of the GTU */
483     @Override
484     public final String getId()
485     {
486         return this.id;
487     }
488     
489     /**
490      * Sets a tag, these are used for specific use cases of any sort.
491      * @param tag String; name of the tag.
492      * @param value String; value of the tag.
493      */
494     public void setTag(final String tag, final String value)
495     {
496         this.tags.put(tag, value);
497     }
498     
499     /**
500      * Returns the value for the given tag, these are used for specific use cases of any sort.
501      * @param tag String; name of the tag.
502      * @return String; value of the tag, or {@code null} if it is not given to the GTU.
503      */
504     public String getTag(final String tag)
505     {
506         return this.tags.get(tag);
507     }
508 
509     /** {@inheritDoc} */
510     @SuppressWarnings("checkstyle:designforextension")
511     @Override
512     public GtuType getType()
513     {
514         return this.gtuType;
515     }
516 
517     /** @return the reference position of the GTU, by definition (0, 0, 0). */
518     public final RelativePosition getReference()
519     {
520         return RelativePosition.REFERENCE_POSITION;
521     }
522 
523     /** @return the simulator of the GTU. */
524     public final OtsSimulatorInterface getSimulator()
525     {
526         return this.simulator;
527     }
528 
529     /** @return Parameters. */
530     public final Parameters getParameters()
531     {
532         return this.parameters;
533     }
534 
535     /** @param parameters Parameters; parameters */
536     public final void setParameters(final Parameters parameters)
537     {
538         this.parameters = parameters;
539     }
540 
541     /**
542      * @return StrategicalPlanner; the planner responsible for the overall 'mission' of the GTU, usually indicating where it
543      *         needs to go. It operates by instantiating tactical planners to do the work.
544      */
545     public StrategicalPlanner getStrategicalPlanner()
546     {
547         return this.strategicalPlanner.get();
548     }
549 
550     /**
551      * @param time Time; time to obtain the strategical planner at
552      * @return StrategicalPlanner; the planner responsible for the overall 'mission' of the GTU, usually indicating where it
553      *         needs to go. It operates by instantiating tactical planners to do the work.
554      */
555     public StrategicalPlanner getStrategicalPlanner(final Time time)
556     {
557         return this.strategicalPlanner.get(time);
558     }
559 
560     /** @return TacticalPlanner; the current tactical planner that can generate an operational plan */
561     public TacticalPlanner<?, ?> getTacticalPlanner()
562     {
563         return getStrategicalPlanner().getTacticalPlanner();
564     }
565 
566     /**
567      * @param time Time; time to obtain the tactical planner at
568      * @return TacticalPlanner; the tactical planner that can generate an operational plan at the given time
569      */
570     public TacticalPlanner<?, ?> getTacticalPlanner(final Time time)
571     {
572         return getStrategicalPlanner(time).getTacticalPlanner(time);
573     }
574 
575     /** @return the current operational plan for the GTU */
576     public final OperationalPlan getOperationalPlan()
577     {
578         return this.operationalPlan.get();
579     }
580 
581     /**
582      * @param time Time; time to obtain the operational plan at
583      * @return the operational plan for the GTU at the given time.
584      */
585     public final OperationalPlan getOperationalPlan(final Time time)
586     {
587         return this.operationalPlan.get(time);
588     }
589 
590     /**
591      * Set the operational plan. This method is for sub classes.
592      * @param operationalPlan OperationalPlan; operational plan.
593      */
594     protected void setOperationalPlan(final OperationalPlan operationalPlan)
595     {
596         this.operationalPlan.set(operationalPlan);
597     }
598 
599     /**
600      * @return Length; the current odometer value.
601      */
602     public final Length getOdometer()
603     {
604         return getOdometer(this.simulator.getSimulatorAbsTime());
605     }
606 
607     /**
608      * @param time Time; time to obtain the odometer at
609      * @return Length; the odometer value at given time.
610      */
611     public final Length getOdometer(final Time time)
612     {
613         synchronized (this)
614         {
615             if (getOperationalPlan(time) == null)
616             {
617                 return this.odometer.get(time);
618             }
619             try
620             {
621                 return this.odometer.get(time).plus(getOperationalPlan(time).getTraveledDistance(time));
622             }
623             catch (OperationalPlanException ope)
624             {
625                 return this.odometer.get(time);
626             }
627         }
628     }
629 
630     /** @return the current speed of the GTU, along the direction of movement. */
631     public final Speed getSpeed()
632     {
633         synchronized (this)
634         {
635             return getSpeed(this.simulator.getSimulatorAbsTime());
636         }
637     }
638 
639     /**
640      * @param time Time; time at which to obtain the speed
641      * @return the current speed of the GTU, along the direction of movement.
642      */
643     public final Speed getSpeed(final Time time)
644     {
645         synchronized (this)
646         {
647             if (this.cachedSpeedTime != time.si)
648             {
649                 // Invalidate everything
650                 this.cachedSpeedTime = Double.NaN;
651                 this.cachedSpeed = null;
652                 OperationalPlan plan = getOperationalPlan(time);
653                 if (plan == null)
654                 {
655                     this.cachedSpeed = Speed.ZERO;
656                 }
657                 else if (time.si < plan.getStartTime().si)
658                 {
659                     this.cachedSpeed = plan.getStartSpeed();
660                 }
661                 else if (time.si > plan.getEndTime().si)
662                 {
663                     if (time.si - plan.getEndTime().si < 1e-6)
664                     {
665                         this.cachedSpeed = Try.assign(() -> plan.getSpeed(plan.getEndTime()),
666                                 "getSpeed() could not derive a valid speed for the current operationalPlan");
667                     }
668                     else
669                     {
670                         throw new IllegalStateException("Requesting speed value beyond plan.");
671                     }
672                 }
673                 else
674                 {
675                     this.cachedSpeed = Try.assign(() -> plan.getSpeed(time),
676                             "getSpeed() could not derive a valid speed for the current operationalPlan");
677                 }
678                 this.cachedSpeedTime = time.si; // Do this last
679             }
680             return this.cachedSpeed;
681         }
682     }
683 
684     /** @return the current acceleration of the GTU, along the direction of movement. */
685     public final Acceleration getAcceleration()
686     {
687         synchronized (this)
688         {
689             return getAcceleration(this.simulator.getSimulatorAbsTime());
690         }
691     }
692 
693     /**
694      * @param time Time; time at which to obtain the acceleration
695      * @return the current acceleration of the GTU, along the direction of movement.
696      */
697     public final Acceleration getAcceleration(final Time time)
698     {
699         synchronized (this)
700         {
701             if (this.cachedAccelerationTime != time.si)
702             {
703                 // Invalidate everything
704                 this.cachedAccelerationTime = Double.NaN;
705                 this.cachedAcceleration = null;
706                 OperationalPlan plan = getOperationalPlan(time);
707                 if (plan == null)
708                 {
709                     this.cachedAcceleration = Acceleration.ZERO;
710                 }
711                 else if (time.si < plan.getStartTime().si)
712                 {
713                     this.cachedAcceleration =
714                             Try.assign(() -> plan.getAcceleration(plan.getStartTime()), "Exception obtaining acceleration.");
715                 }
716                 else if (time.si > plan.getEndTime().si)
717                 {
718                     if (time.si - plan.getEndTime().si < 1e-6)
719                     {
720                         this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(plan.getEndTime()),
721                                 "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
722                     }
723                     else
724                     {
725                         throw new IllegalStateException("Requesting acceleration value beyond plan.");
726                     }
727                 }
728                 else
729                 {
730                     this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(time),
731                             "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
732                 }
733                 this.cachedAccelerationTime = time.si;
734             }
735             return this.cachedAcceleration;
736         }
737     }
738 
739     /**
740      * @return maximumAcceleration
741      */
742     public final Acceleration getMaximumAcceleration()
743     {
744         return this.maximumAcceleration;
745     }
746 
747     /**
748      * @param maximumAcceleration Acceleration; set maximumAcceleration
749      */
750     public final void setMaximumAcceleration(final Acceleration maximumAcceleration)
751     {
752         if (maximumAcceleration.le(Acceleration.ZERO))
753         {
754             throw new RuntimeException("Maximum acceleration of GTU " + this.id + " set to value <= 0");
755         }
756         this.maximumAcceleration = maximumAcceleration;
757     }
758 
759     /**
760      * @return maximumDeceleration
761      */
762     public final Acceleration getMaximumDeceleration()
763     {
764         return this.maximumDeceleration;
765     }
766 
767     /**
768      * Set the maximum deceleration.
769      * @param maximumDeceleration Acceleration; set maximumDeceleration, must be a negative number
770      */
771     public final void setMaximumDeceleration(final Acceleration maximumDeceleration)
772     {
773         if (maximumDeceleration.ge(Acceleration.ZERO))
774         {
775             throw new RuntimeException("Cannot set maximum deceleration of GTU " + this.id + " to " + maximumDeceleration
776                     + " (value must be negative)");
777         }
778         this.maximumDeceleration = maximumDeceleration;
779     }
780 
781     /** Cache location time. */
782     private Time cacheLocationTime = new Time(Double.NaN, TimeUnit.DEFAULT);
783 
784     /** Cached location at that time. */
785     private DirectedPoint cacheLocation = null;
786 
787     /** {@inheritDoc} */
788     @Override
789     @SuppressWarnings("checkstyle:designforextension")
790     public DirectedPoint getLocation()
791     {
792         synchronized (this)
793         {
794             if (this.operationalPlan.get() == null)
795             {
796                 this.simulator.getLogger().always()
797                         .error("No operational plan for GTU " + this.id + " at t=" + this.getSimulator().getSimulatorTime());
798                 return new DirectedPoint(0, 0, 0); // Do not cache it
799             }
800             try
801             {
802                 // cache
803                 Time locationTime = this.simulator.getSimulatorAbsTime();
804                 if (null == this.cacheLocationTime || this.cacheLocationTime.si != locationTime.si)
805                 {
806                     this.cacheLocationTime = null;
807                     this.cacheLocation = this.operationalPlan.get().getLocation(locationTime);
808                     this.cacheLocationTime = locationTime;
809                 }
810                 return this.cacheLocation;
811             }
812             catch (OperationalPlanException exception)
813             {
814                 return new DirectedPoint(0, 0, 0);
815             }
816         }
817     }
818 
819     /**
820      * Return the shape of a dynamic object at time 'time'. Note that the getShape() method without a time returns the Minkowski
821      * sum of all shapes of the spatial object for a validity time window, e.g., a contour that describes all locations of a GTU
822      * for the next time step, i.e., the contour of the GTU belonging to the next operational plan.
823      * @param time Time; the time for which we want the shape
824      * @return OtsShape; the shape of the object at time 'time'
825      */
826     @Override
827     public OtsShape getShape(final Time time)
828     {
829         try
830         {
831             if (this.shape == null)
832             {
833                 double w = getWidth().si;
834                 double l = getLength().si;
835                 this.shape = new OtsShape(new OtsPoint3d(-0.5 * l, -0.5 * w, 0.0), new OtsPoint3d(-0.5 * l, 0.5 * w, 0.0),
836                         new OtsPoint3d(0.5 * l, 0.5 * w, 0.0), new OtsPoint3d(0.5 * l, -0.5 * w, 0.0));
837             }
838             OtsShape s = transformShape(this.shape, this.operationalPlan.get(time).getLocation(time));
839             System.out.println("gtu " + getId() + ", shape(t)=" + s);
840             return s;
841         }
842         catch (OtsGeometryException | OperationalPlanException exception)
843         {
844             throw new RuntimeException(exception);
845         }
846     }
847 
848     /**
849      * Return the shape of the GTU for the validity time of the operational plan. Note that this method without a time returns
850      * the Minkowski sum of all shapes of the spatial object for a validity time window, e.g., a contour that describes all
851      * locations of a GTU for the next time step, i.e., the contour of the GTU belonging to the next operational plan.
852      * @return OtsShape; the shape of the object over the validity of the operational plan
853      */
854     @Override
855     public OtsShape getShape()
856     {
857         try
858         {
859             // TODO: the actual contour of the GTU has to be moved over the path
860             OtsLine3d path = this.operationalPlan.get().getPath();
861             // part of the Gtu length has to be added before the start and after the end of the path.
862             // we assume the reference point is within the contour of the Gtu.
863             double rear = Math.max(0.0, getReference().getDx().si - getRear().getDx().si);
864             double front = path.getLengthSI() + Math.max(0.0, getFront().getDx().si - getReference().getDx().si);
865             DirectedPoint p0 = path.getLocationExtendedSI(-rear);
866             DirectedPoint pn = path.getLocationExtendedSI(front);
867             List<OtsPoint3d> pList = new ArrayList<>(Arrays.asList(path.getPoints()));
868             pList.add(0, new OtsPoint3d(p0));
869             pList.add(new OtsPoint3d(pn));
870             OtsLine3d extendedPath = new OtsLine3d(pList);
871             List<OtsPoint3d> swath = new ArrayList<>();
872             swath.addAll(Arrays.asList(extendedPath.offsetLine(getWidth().si / 2.0).getPoints()));
873             swath.addAll(Arrays.asList(extendedPath.offsetLine(-getWidth().si / 2.0).reverse().getPoints()));
874             OtsShape s = new OtsShape(swath);
875             // System.out.println("gtu " + getId() + ", w=" + getWidth() + ", path="
876             // + this.operationalPlan.get().getPath().toString() + ", shape=" + s);
877             return s;
878         }
879         catch (Exception e)
880         {
881             throw new RuntimeException(e);
882         }
883     }
884 
885     /**
886      * Returns whether the GTU is destroyed.
887      * @return whether the GTU is destroyed
888      */
889     public final boolean isDestroyed()
890     {
891         return this.destroyed;
892     }
893 
894     /** @return the context to which the GTU belongs */
895     public PerceivableContext getPerceivableContext()
896     {
897         return this.perceivableContext;
898     }
899 
900     /**
901      * Adds the provided GTU to this GTU, meaning it moves with this GTU.
902      * @param gtu Gtu; gtu to enter this GTU
903      * @throws GtuException if the gtu already has a parent
904      */
905     public void addGtu(final Gtu gtu) throws GtuException
906     {
907         this.children.add(gtu);
908         gtu.setParent(this);
909     }
910 
911     /**
912      * Removes the provided GTU from this GTU, meaning it no longer moves with this GTU.
913      * @param gtu Gtu; gtu to exit this GTU
914      */
915     public void removeGtu(final Gtu gtu)
916     {
917         this.children.remove(gtu);
918         try
919         {
920             gtu.setParent(null);
921         }
922         catch (GtuException exception)
923         {
924             // cannot happen, setting null is always ok
925         }
926     }
927 
928     /**
929      * Set the parent GTU.
930      * @param gtu Gtu; parent GTU, may be {@code null}
931      * @throws GtuException if the gtu already has a parent
932      */
933     public void setParent(final Gtu gtu) throws GtuException
934     {
935         Throw.when(gtu != null && this.parent != null, GtuException.class, "GTU %s already has a parent.", this);
936         this.parent = gtu;
937     }
938 
939     /**
940      * Returns the parent GTU, or {@code null} if this GTU has no parent.
941      * @return Gtu; parent GTU, or {@code null} if this GTU has no parent
942      */
943     public Gtu getParent()
944     {
945         return this.parent;
946     }
947 
948     /**
949      * Returns the children GTU's.
950      * @return Set&lt;GTU&gt;; children GTU's
951      */
952     public Set<Gtu> getChildren()
953     {
954         return new LinkedHashSet<>(this.children); // safe copy
955     }
956 
957     /**
958      * @return errorHandler.
959      */
960     protected GtuErrorHandler getErrorHandler()
961     {
962         return this.errorHandler;
963     }
964 
965     /**
966      * Sets the error handler.
967      * @param errorHandler GTUErrorHandler; error handler
968      */
969     public void setErrorHandler(final GtuErrorHandler errorHandler)
970     {
971         this.errorHandler = errorHandler;
972     }
973 
974     /**
975      * Note that destroying the next move event of the GTU can be dangerous!
976      * @return nextMoveEvent the next move event of the GTU, e.g. to cancel it from outside.
977      */
978     public final SimEvent<Duration> getNextMoveEvent()
979     {
980         return this.nextMoveEvent;
981     }
982 
983     /** {@inheritDoc} */
984     @Override
985     @SuppressWarnings("designforextension")
986     public int hashCode()
987     {
988         final int prime = 31;
989         int result = 1;
990         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
991         result = prime * result + this.uniqueNumber;
992         return result;
993     }
994 
995     /** {@inheritDoc} */
996     @Override
997     @SuppressWarnings({"designforextension", "needbraces"})
998     public boolean equals(final Object obj)
999     {
1000         if (this == obj)
1001             return true;
1002         if (obj == null)
1003             return false;
1004         if (getClass() != obj.getClass())
1005             return false;
1006         Gtu other = (Gtu) obj;
1007         if (this.id == null)
1008         {
1009             if (other.id != null)
1010                 return false;
1011         }
1012         else if (!this.id.equals(other.id))
1013             return false;
1014         if (this.uniqueNumber != other.uniqueNumber)
1015             return false;
1016         return true;
1017     }
1018 
1019     /**
1020      * The event type for pub/sub indicating a move. <br>
1021      * Payload: [String id, DirectedPoint position, Speed speed, Acceleration acceleration, Length odometer]
1022      */
1023     public static EventType MOVE_EVENT = new EventType("GTU.MOVE",
1024             new MetaData("GTU move", "GTU id, position, speed, acceleration, odometer",
1025                     new ObjectDescriptor[] {new ObjectDescriptor("Id", "GTU Id", String.class),
1026                             new ObjectDescriptor("position", "position", PositionVector.class),
1027                             new ObjectDescriptor("direction", "direction", Direction.class),
1028                             new ObjectDescriptor("speed", "speed", Speed.class),
1029                             new ObjectDescriptor("acceleration", "acceleration", Acceleration.class),
1030                             new ObjectDescriptor("Odometer", "Total distance travelled since incarnation", Length.class)}));
1031 
1032     /**
1033      * The event type for pub/sub indicating destruction of the GTU. <br>
1034      * Payload: [String id, DirectedPoint lastPosition, Length odometer]
1035      */
1036     public static EventType DESTROY_EVENT = new EventType("GTU.DESTROY",
1037             new MetaData("GTU destroy", "GTU id, final position, final odometer",
1038                     new ObjectDescriptor[] {new ObjectDescriptor("Id", "GTU Id", String.class),
1039                             new ObjectDescriptor("position", "position", PositionVector.class),
1040                             new ObjectDescriptor("direction", "direction", Direction.class),
1041                             new ObjectDescriptor("Odometer", "Total distance travelled since incarnation", Length.class)}));
1042 
1043 }