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