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