View Javadoc
1   package org.opentrafficsim.core.gtu;
2   
3   import java.io.Serializable;
4   import java.util.LinkedHashSet;
5   import java.util.Set;
6   
7   import org.djunits.unit.DirectionUnit;
8   import org.djunits.unit.DurationUnit;
9   import org.djunits.unit.PositionUnit;
10  import org.djunits.unit.TimeUnit;
11  import org.djunits.value.vdouble.scalar.Acceleration;
12  import org.djunits.value.vdouble.scalar.Direction;
13  import org.djunits.value.vdouble.scalar.Duration;
14  import org.djunits.value.vdouble.scalar.Length;
15  import org.djunits.value.vdouble.scalar.Speed;
16  import org.djunits.value.vdouble.scalar.Time;
17  import org.djutils.event.EventProducer;
18  import org.djutils.exceptions.Throw;
19  import org.djutils.exceptions.Try;
20  import org.opentrafficsim.base.parameters.ParameterException;
21  import org.opentrafficsim.base.parameters.Parameters;
22  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
23  import org.opentrafficsim.core.geometry.DirectedPoint;
24  import org.opentrafficsim.core.geometry.OTSPoint3D;
25  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
26  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
27  import org.opentrafficsim.core.gtu.plan.strategical.StrategicalPlanner;
28  import org.opentrafficsim.core.gtu.plan.tactical.TacticalPlanner;
29  import org.opentrafficsim.core.idgenerator.IdGenerator;
30  import org.opentrafficsim.core.network.NetworkException;
31  import org.opentrafficsim.core.perception.Historical;
32  import org.opentrafficsim.core.perception.HistoricalValue;
33  import org.opentrafficsim.core.perception.HistoryManager;
34  import org.opentrafficsim.core.perception.PerceivableContext;
35  
36  import nl.tudelft.simulation.dsol.SimRuntimeException;
37  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
38  
39  /**
40   * Implements the basic functionalities of any GTU: the ability to move on 3D-space according to a plan.
41   * <p>
42   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
43   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
44   * <p>
45   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Oct 22, 2014 <br>
46   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
47   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
48   */
49  public abstract class AbstractGTU extends EventProducer implements GTU
50  {
51      /** */
52      private static final long serialVersionUID = 20140822L;
53  
54      /** The id of the GTU. */
55      private final String id;
56  
57      /** unique number of the GTU. */
58      private final int uniqueNumber;
59  
60      /** the unique number counter. */
61      private static int staticUNIQUENUMBER = 0;
62  
63      /** The type of GTU, e.g. TruckType, CarType, BusType. */
64      private final GTUType gtuType;
65  
66      /** The simulator to schedule activities on. */
67      private final OTSSimulatorInterface simulator;
68  
69      /** Model parameters. */
70      private Parameters parameters;
71  
72      /** The maximum acceleration. */
73      private Acceleration maximumAcceleration;
74  
75      /** The maximum deceleration, stored as a negative number. */
76      private Acceleration maximumDeceleration;
77  
78      /**
79       * The odometer which measures how much distance have we covered between instantiation and the last completed operational
80       * plan. In order to get a complete odometer reading, the progress of the current plan execution has to be added to this
81       * value.
82       */
83      private Historical<Length> odometer;
84  
85      /** The strategical planner that can instantiate tactical planners to determine mid-term decisions. */
86      private final Historical<StrategicalPlanner> strategicalPlanner;
87  
88      /** The tactical planner that can generate an operational plan. */
89      private final Historical<TacticalPlanner<?, ?>> tacticalPlanner;
90  
91      /** The current operational plan, which provides a short-term movement over time. */
92      protected final Historical<OperationalPlan> operationalPlan;
93  
94      /** The next move event as scheduled on the simulator, can be used for interrupting the current move. */
95      private SimEvent<Duration> nextMoveEvent;
96  
97      /** The model in which this GTU is registered. */
98      private PerceivableContext perceivableContext;
99  
100     /** Is this GTU destroyed? */
101     private boolean destroyed = false;
102 
103     /** aligned or not. */
104     // TODO: should be indicated with a Parameter
105     public static boolean ALIGNED = true;
106 
107     /** aligned schedule count. */
108     // TODO: can be removed after testing period
109     public static int ALIGN_COUNT = 0;
110 
111     /** Cached speed time. */
112     private double cachedSpeedTime = Double.NaN;
113 
114     /** Cached speed. */
115     private Speed cachedSpeed = null;
116 
117     /** Cached acceleration time. */
118     private double cachedAccelerationTime = Double.NaN;
119 
120     /** Cached acceleration. */
121     private Acceleration cachedAcceleration = null;
122 
123     /** Parent GTU. */
124     private GTU parent = null;
125 
126     /** Children GTU's. */
127     private Set<GTU> children = new LinkedHashSet<>();
128 
129     /** Error handler. */
130     private GTUErrorHandler errorHandler = GTUErrorHandler.THROW;
131 
132     /**
133      * @param id String; the id of the GTU
134      * @param gtuType GTUType; the type of GTU, e.g. TruckType, CarType, BusType
135      * @param simulator OTSSimulatorInterface; the simulator to schedule plan changes on
136      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
137      * @throws GTUException when the preconditions of the constructor are not met
138      */
139     @SuppressWarnings("checkstyle:parameternumber")
140     public AbstractGTU(final String id, final GTUType gtuType, final OTSSimulatorInterface simulator,
141             final PerceivableContext perceivableContext) throws GTUException
142     {
143         Throw.when(id == null, GTUException.class, "id is null");
144         Throw.when(gtuType == null, GTUException.class, "gtuType is null");
145         Throw.when(perceivableContext == null, GTUException.class, "perceivableContext is null for GTU with id %s", id);
146         Throw.when(perceivableContext.containsGtuId(id), GTUException.class,
147                 "GTU with id %s already registered in perceivableContext %s", id, perceivableContext.getId());
148         Throw.when(simulator == null, GTUException.class, "simulator is null for GTU with id %s", id);
149 
150         HistoryManager historyManager = simulator.getReplication().getHistoryManager(simulator);
151         this.id = id;
152         this.uniqueNumber = ++staticUNIQUENUMBER;
153         this.gtuType = gtuType;
154         this.simulator = simulator;
155         this.odometer = new HistoricalValue<>(historyManager, Length.ZERO);
156         this.perceivableContext = perceivableContext;
157         this.perceivableContext.addGTU(this);
158         this.strategicalPlanner = new HistoricalValue<>(historyManager);
159         this.tacticalPlanner = new HistoricalValue<>(historyManager, null);
160         this.operationalPlan = new HistoricalValue<>(historyManager, null);
161     }
162 
163     /**
164      * @param idGenerator IdGenerator; the generator that will produce a unique id of the GTU
165      * @param gtuType GTUType; the type of GTU, e.g. TruckType, CarType, BusType
166      * @param simulator OTSSimulatorInterface; the simulator to schedule plan changes on
167      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
168      * @throws GTUException when the preconditions of the constructor are not met
169      */
170     @SuppressWarnings("checkstyle:parameternumber")
171     public AbstractGTU(final IdGenerator idGenerator, final GTUType gtuType, final OTSSimulatorInterface simulator,
172             final PerceivableContext perceivableContext) throws GTUException
173     {
174         this(generateId(idGenerator), gtuType, simulator, perceivableContext);
175     }
176 
177     /**
178      * Initialize the GTU at a location and speed, and give it a mission to fulfill through the strategical planner.
179      * @param strategicalPlanner StrategicalPlanner; the strategical planner responsible for the overall 'mission' of the GTU,
180      *            usually indicating where it needs to go. It operates by instantiating tactical planners to do the work.
181      * @param initialLocation DirectedPoint; the initial location (and direction) of the GTU
182      * @param initialSpeed Speed; the initial speed of the GTU
183      * @throws SimRuntimeException when scheduling after the first move fails
184      * @throws GTUException when the preconditions of the parameters are not met or when the construction of the original
185      *             waiting path fails
186      */
187     @SuppressWarnings({"checkstyle:hiddenfield", "hiding", "checkstyle:designforextension"})
188     public void init(final StrategicalPlanner strategicalPlanner, final DirectedPoint initialLocation, final Speed initialSpeed)
189             throws SimRuntimeException, GTUException
190     {
191         Throw.when(strategicalPlanner == null, GTUException.class, "strategicalPlanner is null for GTU with id %s", this.id);
192         Throw.whenNull(initialLocation, "Initial location of GTU cannot be null");
193         Throw.when(Double.isNaN(initialLocation.x) || Double.isNaN(initialLocation.y) || Double.isNaN(initialLocation.z),
194                 GTUException.class, "initialLocation %s invalid for GTU with id %s", initialLocation, this.id);
195         Throw.when(initialSpeed == null, GTUException.class, "initialSpeed is null for GTU with id %s", this.id);
196         Throw.when(!getId().equals(strategicalPlanner.getGtu().getId()), GTUException.class,
197                 "GTU %s is initialized with a strategical planner for GTU %s", getId(), strategicalPlanner.getGtu().getId());
198 
199         this.strategicalPlanner.set(strategicalPlanner);
200         this.tacticalPlanner.set(strategicalPlanner.getTacticalPlanner());
201         Time now = this.simulator.getSimulatorAbsTime();
202 
203         DirectedPoint location = getLocation();
204         fireTimedEvent(GTU.INIT_EVENT, new Object[] {getId(), new OTSPoint3D(location).doubleVector(PositionUnit.METER),
205                 new Direction(location.getZ(), DirectionUnit.EAST_RADIAN), getLength(), getWidth()}, now);
206 
207         try
208         {
209             move(initialLocation);
210         }
211         catch (OperationalPlanException | NetworkException | ParameterException exception)
212         {
213             throw new GTUException("Failed to create OperationalPlan for GTU " + this.id, exception);
214         }
215     }
216 
217     /**
218      * Generate an id, but check first that we have a valid IdGenerator.
219      * @param idGenerator IdGenerator; the generator that will produce a unique id of the GTU
220      * @return a (hopefully unique) Id of the GTU
221      * @throws GTUException when the idGenerator is null
222      */
223     private static String generateId(final IdGenerator idGenerator) throws GTUException
224     {
225         Throw.when(idGenerator == null, GTUException.class, "AbstractGTU.<init>: idGenerator is null");
226         return idGenerator.nextId();
227     }
228 
229     /**
230      * Destructor. Don't forget to call with super.destroy() from any override to avoid memory leaks in the network.
231      */
232     @Override
233     @SuppressWarnings("checkstyle:designforextension")
234     public void destroy()
235     {
236         DirectedPoint location = getLocation();
237         fireTimedEvent(GTU.DESTROY_EVENT,
238                 new Object[] {getId(), new OTSPoint3D(location).doubleVector(PositionUnit.METER),
239                         new Direction(location.getZ(), DirectionUnit.EAST_RADIAN), getOdometer()},
240                 this.simulator.getSimulatorTime());
241 
242         // cancel the next move
243         if (this.nextMoveEvent != null)
244         {
245             this.simulator.cancelEvent(this.nextMoveEvent);
246             this.nextMoveEvent = null;
247         }
248 
249         this.perceivableContext.removeGTU(this);
250         this.destroyed = true;
251     }
252 
253     /**
254      * Move from the current location according to an operational plan to a location that will bring us nearer to reaching the
255      * location provided by the strategical planner. <br>
256      * This method can be overridden to carry out specific behavior during the execution of the plan (e.g., scheduling of
257      * triggers, entering or leaving lanes, etc.). Please bear in mind that the call to super.move() is essential, and that one
258      * has to take care to handle the situation that the plan gets interrupted.
259      * @param fromLocation DirectedPoint; the last known location (initial location, or end location of the previous operational
260      *            plan)
261      * @return boolean; whether an exception occurred
262      * @throws SimRuntimeException when scheduling of the next move fails
263      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
264      * @throws GTUException when there is a problem with the state of the GTU when planning a path
265      * @throws NetworkException in case of a problem with the network, e.g., a dead end where it is not expected
266      * @throws ParameterException in there is a parameter problem
267      */
268     @SuppressWarnings("checkstyle:designforextension")
269     protected boolean move(final DirectedPoint fromLocation)
270             throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
271     {
272         try
273         {
274             Time now = this.simulator.getSimulatorAbsTime();
275 
276             // Add the odometer distance from the currently running operational plan.
277             // Because a plan can be interrupted, we explicitly calculate the covered distance till 'now'
278             Length currentOdometer;
279             if (this.operationalPlan.get() != null)
280             {
281                 currentOdometer = this.odometer.get().plus(this.operationalPlan.get().getTraveledDistance(now));
282             }
283             else
284             {
285                 currentOdometer = this.odometer.get();
286             }
287 
288             // Do we have an operational plan?
289             // TODO discuss when a new tactical planner may be needed
290             TacticalPlanner<?, ?> tactPlanner = this.tacticalPlanner.get();
291             if (tactPlanner == null)
292             {
293                 // Tell the strategical planner to provide a tactical planner
294                 tactPlanner = this.strategicalPlanner.get().getTacticalPlanner();
295                 this.tacticalPlanner.set(tactPlanner);
296             }
297             synchronized (this)
298             {
299                 tactPlanner.getPerception().perceive();
300             }
301             OperationalPlan newOperationalPlan = tactPlanner.generateOperationalPlan(now, fromLocation);
302             synchronized (this)
303             {
304                 this.operationalPlan.set(newOperationalPlan);
305                 this.cachedSpeedTime = Double.NaN;
306                 this.cachedAccelerationTime = Double.NaN;
307                 this.odometer.set(currentOdometer);
308             }
309 
310             // TODO allow alignment at different intervals, also different between GTU's within a single simulation
311             if (ALIGNED && newOperationalPlan.getTotalDuration().si == 0.5)
312             {
313                 // schedule the next move at exactly 0.5 seconds on the clock
314                 // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
315                 double tNext = Math.floor(2.0 * now.si + 1.0) / 2.0;
316                 DirectedPoint p = (tNext - now.si < 0.5) ? newOperationalPlan.getEndLocation()
317                         : newOperationalPlan.getLocation(new Duration(tNext - now.si, DurationUnit.SI));
318                 this.nextMoveEvent = new SimEvent<Duration>(
319                         new Duration(tNext - getSimulator().getStartTimeAbs().si, DurationUnit.SI), this, this,
320                         "move", new Object[] {p});
321                 ALIGN_COUNT++;
322             }
323             else
324             {
325                 // schedule the next move at the end of the current operational plan
326                 // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
327                 this.nextMoveEvent = new SimEvent<>(
328                         now.plus(newOperationalPlan.getTotalDuration())
329                                 .minus(getSimulator().getStartTimeAbs()),
330                         this, this, "move", new Object[] {newOperationalPlan.getEndLocation()});
331             }
332             this.simulator.scheduleEvent(this.nextMoveEvent);
333             fireTimedEvent(GTU.MOVE_EVENT,
334                     new Object[] {getId(), new OTSPoint3D(fromLocation).doubleVector(PositionUnit.METER),
335                             new Direction(fromLocation.getZ(), DirectionUnit.EAST_RADIAN), getSpeed(), getAcceleration(),
336                             getOdometer()},
337                     this.simulator.getSimulatorTime());
338 
339             return false;
340         }
341         catch (Exception ex)
342         {
343             try
344             {
345                 this.errorHandler.handle(this, ex);
346             }
347             catch (Exception exception)
348             {
349                 throw new GTUException(exception);
350             }
351             return true;
352         }
353     }
354 
355     /**
356      * Interrupt the move and ask for a new plan. This method can be overridden to carry out the bookkeeping needed when the
357      * current plan gets interrupted.
358      * @throws OperationalPlanException when there was a problem retrieving the location from the running plan
359      * @throws SimRuntimeException when scheduling of the next move fails
360      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
361      * @throws GTUException when there is a problem with the state of the GTU when planning a path
362      * @throws NetworkException in case of a problem with the network, e.g., unreachability of a certain point
363      * @throws ParameterException when there is a problem with a parameter
364      */
365     @SuppressWarnings("checkstyle:designforextension")
366     protected void interruptMove()
367             throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
368     {
369         this.simulator.cancelEvent(this.nextMoveEvent);
370         move(this.operationalPlan.get().getLocation(this.simulator.getSimulatorTime()));
371     }
372 
373     /** {@inheritDoc} */
374     @Override
375     public final String getId()
376     {
377         return this.id;
378     }
379 
380     /** {@inheritDoc} */
381     @SuppressWarnings("checkstyle:designforextension")
382     @Override
383     public GTUType getGTUType()
384     {
385         return this.gtuType;
386     }
387 
388     /** {@inheritDoc} */
389     @Override
390     public final RelativePosition getReference()
391     {
392         return RelativePosition.REFERENCE_POSITION;
393     }
394 
395     /** {@inheritDoc} */
396     @Override
397     public final OTSSimulatorInterface getSimulator()
398     {
399         return this.simulator;
400     }
401 
402     /** {@inheritDoc} */
403     @Override
404     public final Parameters getParameters()
405     {
406         return this.parameters;
407     }
408 
409     /** {@inheritDoc} */
410     @Override
411     public final void setParameters(final Parameters parameters)
412     {
413         this.parameters = parameters;
414     }
415 
416     /** {@inheritDoc} */
417     @Override
418     public StrategicalPlanner getStrategicalPlanner()
419     {
420         return this.strategicalPlanner.get();
421     }
422 
423     /** {@inheritDoc} */
424     @Override
425     public StrategicalPlanner getStrategicalPlanner(final Time time)
426     {
427         return this.strategicalPlanner.get(time);
428     }
429 
430     /** {@inheritDoc} */
431     @Override
432     public final OperationalPlan getOperationalPlan()
433     {
434         return this.operationalPlan.get();
435     }
436 
437     /** {@inheritDoc} */
438     @Override
439     public final OperationalPlan getOperationalPlan(final Time time)
440     {
441         return this.operationalPlan.get(time);
442     }
443 
444     /** {@inheritDoc} */
445     @Override
446     public final Length getOdometer()
447     {
448         return getOdometer(this.simulator.getSimulatorAbsTime());
449     }
450 
451     /** {@inheritDoc} */
452     @Override
453     public final Length getOdometer(final Time time)
454     {
455         synchronized (this)
456         {
457             if (getOperationalPlan(time) == null)
458             {
459                 return this.odometer.get(time);
460             }
461             try
462             {
463                 return this.odometer.get(time).plus(getOperationalPlan(time).getTraveledDistance(time));
464             }
465             catch (OperationalPlanException ope)
466             {
467                 return this.odometer.get(time);
468             }
469         }
470     }
471 
472     /** {@inheritDoc} */
473     @Override
474     public final Speed getSpeed()
475     {
476         synchronized (this)
477         {
478             return getSpeed(this.simulator.getSimulatorAbsTime());
479         }
480     }
481 
482     /** {@inheritDoc} */
483     @Override
484     public final Speed getSpeed(final Time time)
485     {
486         synchronized (this)
487         {
488             if (this.cachedSpeedTime != time.si)
489             {
490                 // Invalidate everything
491                 this.cachedSpeedTime = Double.NaN;
492                 this.cachedSpeed = null;
493                 OperationalPlan plan = getOperationalPlan(time);
494                 if (plan == null)
495                 {
496                     this.cachedSpeed = Speed.ZERO;
497                 }
498                 else if (time.si < plan.getStartTime().si)
499                 {
500                     this.cachedSpeed = plan.getStartSpeed();
501                 }
502                 else if (time.si > plan.getEndTime().si)
503                 {
504                     if (time.si - plan.getEndTime().si < 1e-6)
505                     {
506                         this.cachedSpeed = Try.assign(() -> plan.getSpeed(plan.getEndTime()),
507                                 "getSpeed() could not derive a valid speed for the current operationalPlan");
508                     }
509                     else
510                     {
511                         throw new IllegalStateException("Requesting speed value beyond plan.");
512                     }
513                 }
514                 else
515                 {
516                     this.cachedSpeed = Try.assign(() -> plan.getSpeed(time),
517                             "getSpeed() could not derive a valid speed for the current operationalPlan");
518                 }
519                 this.cachedSpeedTime = time.si; // Do this last
520             }
521             return this.cachedSpeed;
522         }
523     }
524 
525     /** {@inheritDoc} */
526     @Override
527     public final Acceleration getAcceleration()
528     {
529         synchronized (this)
530         {
531             return getAcceleration(this.simulator.getSimulatorAbsTime());
532         }
533     }
534 
535     /** {@inheritDoc} */
536     @Override
537     public final Acceleration getAcceleration(final Time time)
538     {
539         synchronized (this)
540         {
541             if (this.cachedAccelerationTime != time.si)
542             {
543                 // Invalidate everything
544                 this.cachedAccelerationTime = Double.NaN;
545                 this.cachedAcceleration = null;
546                 OperationalPlan plan = getOperationalPlan(time);
547                 if (plan == null)
548                 {
549                     this.cachedAcceleration = Acceleration.ZERO;
550                 }
551                 else if (time.si < plan.getStartTime().si)
552                 {
553                     this.cachedAcceleration =
554                             Try.assign(() -> plan.getAcceleration(plan.getStartTime()), "Exception obtaining acceleration.");
555                 }
556                 else if (time.si > plan.getEndTime().si)
557                 {
558                     if (time.si - plan.getEndTime().si < 1e-6)
559                     {
560                         this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(plan.getEndTime()),
561                                 "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
562                     }
563                     else
564                     {
565                         throw new IllegalStateException("Requesting acceleration value beyond plan.");
566                     }
567                 }
568                 else
569                 {
570                     this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(time),
571                             "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
572                 }
573                 this.cachedAccelerationTime = time.si;
574             }
575             return this.cachedAcceleration;
576         }
577     }
578 
579     /**
580      * @return maximumAcceleration
581      */
582     @Override
583     public final Acceleration getMaximumAcceleration()
584     {
585         return this.maximumAcceleration;
586     }
587 
588     /**
589      * @param maximumAcceleration Acceleration; set maximumAcceleration
590      */
591     public final void setMaximumAcceleration(final Acceleration maximumAcceleration)
592     {
593         if (maximumAcceleration.le(Acceleration.ZERO))
594         {
595             throw new RuntimeException("Maximum acceleration of GTU " + this.id + " set to value <= 0");
596         }
597         this.maximumAcceleration = maximumAcceleration;
598     }
599 
600     /**
601      * @return maximumDeceleration
602      */
603     @Override
604     public final Acceleration getMaximumDeceleration()
605     {
606         return this.maximumDeceleration;
607     }
608 
609     /**
610      * Set the maximum deceleration.
611      * @param maximumDeceleration Acceleration; set maximumDeceleration, must be a negative number
612      */
613     public final void setMaximumDeceleration(final Acceleration maximumDeceleration)
614     {
615         if (maximumDeceleration.ge(Acceleration.ZERO))
616         {
617             throw new RuntimeException("Cannot set maximum deceleration of GTU " + this.id + " to " + maximumDeceleration
618                     + " (value must be negative)");
619         }
620         this.maximumDeceleration = maximumDeceleration;
621     }
622 
623     /** Cache location time. */
624     private Time cacheLocationTime = new Time(Double.NaN, TimeUnit.DEFAULT);
625 
626     /** Cached location at that time. */
627     private DirectedPoint cacheLocation = null;
628 
629     /** {@inheritDoc} */
630     @Override
631     @SuppressWarnings("checkstyle:designforextension")
632     public DirectedPoint getLocation()
633     {
634         synchronized (this)
635         {
636             if (this.operationalPlan.get() == null)
637             {
638                 this.simulator.getLogger().always()
639                         .error("No operational plan for GTU " + this.id + " at t=" + this.getSimulator().getSimulatorTime());
640                 return new DirectedPoint(0, 0, 0); // Do not cache it
641             }
642             try
643             {
644                 // cache
645                 Time locationTime = this.simulator.getSimulatorAbsTime();
646                 if (null == this.cacheLocationTime || this.cacheLocationTime.si != locationTime.si)
647                 {
648                     this.cacheLocationTime = null;
649                     this.cacheLocation = this.operationalPlan.get().getLocation(locationTime);
650                     this.cacheLocationTime = locationTime;
651                 }
652                 return this.cacheLocation;
653             }
654             catch (OperationalPlanException exception)
655             {
656                 return new DirectedPoint(0, 0, 0);
657             }
658         }
659     }
660 
661     /** {@inheritDoc} */
662     @Override
663     public final boolean isDestroyed()
664     {
665         return this.destroyed;
666     }
667 
668     /** {@inheritDoc} */
669     @Override
670     public PerceivableContext getPerceivableContext()
671     {
672         return this.perceivableContext;
673     }
674 
675     /** {@inheritDoc} */
676     @Override
677     public void addGtu(final GTU gtu) throws GTUException
678     {
679         this.children.add(gtu);
680         gtu.setParent(this);
681     }
682 
683     /** {@inheritDoc} */
684     @Override
685     public void removeGtu(final GTU gtu)
686     {
687         this.children.remove(gtu);
688         try
689         {
690             gtu.setParent(null);
691         }
692         catch (GTUException exception)
693         {
694             // cannot happen, setting null is always ok
695         }
696     }
697 
698     /** {@inheritDoc} */
699     @Override
700     public void setParent(final GTU gtu) throws GTUException
701     {
702         Throw.when(gtu != null && this.parent != null, GTUException.class, "GTU %s already has a parent.", this);
703         this.parent = gtu;
704     }
705 
706     /** {@inheritDoc} */
707     @Override
708     public GTU getParent()
709     {
710         return this.parent;
711     }
712 
713     /** {@inheritDoc} */
714     @Override
715     public Set<GTU> getChildren()
716     {
717         return new LinkedHashSet<>(this.children); // safe copy
718     }
719 
720     /**
721      * @return errorHandler.
722      */
723     protected GTUErrorHandler getErrorHandler()
724     {
725         return this.errorHandler;
726     }
727 
728     /** {@inheritDoc} */
729     @Override
730     public void setErrorHandler(final GTUErrorHandler errorHandler)
731     {
732         this.errorHandler = errorHandler;
733     }
734 
735     /** {@inheritDoc} */
736     @Override
737     public final Serializable getSourceId()
738     {
739         return this; // TODO: see where the actual pointer to the GTU is needed
740     }
741 
742     /**
743      * Note that destroying the next move event of the GTU can be dangerous!
744      * @return nextMoveEvent the next move event of the GTU, e.g. to cancel it from outside.
745      */
746     public final SimEvent<Duration> getNextMoveEvent()
747     {
748         return this.nextMoveEvent;
749     }
750 
751     /** {@inheritDoc} */
752     @Override
753     @SuppressWarnings("designforextension")
754     public int hashCode()
755     {
756         final int prime = 31;
757         int result = 1;
758         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
759         result = prime * result + this.uniqueNumber;
760         return result;
761     }
762 
763     /** {@inheritDoc} */
764     @Override
765     @SuppressWarnings({"designforextension", "needbraces"})
766     public boolean equals(final Object obj)
767     {
768         if (this == obj)
769             return true;
770         if (obj == null)
771             return false;
772         if (getClass() != obj.getClass())
773             return false;
774         AbstractGTU other = (AbstractGTU) obj;
775         if (this.id == null)
776         {
777             if (other.id != null)
778                 return false;
779         }
780         else if (!this.id.equals(other.id))
781             return false;
782         if (this.uniqueNumber != other.uniqueNumber)
783             return false;
784         return true;
785     }
786 
787 }