AbstractGTU.java

  1. package org.opentrafficsim.core.gtu;

  2. import java.io.Serializable;
  3. import java.util.LinkedHashSet;
  4. import java.util.Set;

  5. import org.djunits.unit.DurationUnit;
  6. import org.djunits.unit.TimeUnit;
  7. import org.djunits.value.vdouble.scalar.Acceleration;
  8. import org.djunits.value.vdouble.scalar.Duration;
  9. import org.djunits.value.vdouble.scalar.Length;
  10. import org.djunits.value.vdouble.scalar.Speed;
  11. import org.djunits.value.vdouble.scalar.Time;
  12. import org.djutils.event.EventProducer;
  13. import org.djutils.exceptions.Throw;
  14. import org.djutils.exceptions.Try;
  15. import org.opentrafficsim.base.parameters.ParameterException;
  16. import org.opentrafficsim.base.parameters.Parameters;
  17. import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
  18. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
  19. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
  20. import org.opentrafficsim.core.gtu.plan.strategical.StrategicalPlanner;
  21. import org.opentrafficsim.core.gtu.plan.tactical.TacticalPlanner;
  22. import org.opentrafficsim.core.idgenerator.IdGenerator;
  23. import org.opentrafficsim.core.network.NetworkException;
  24. import org.opentrafficsim.core.perception.Historical;
  25. import org.opentrafficsim.core.perception.HistoricalValue;
  26. import org.opentrafficsim.core.perception.HistoryManager;
  27. import org.opentrafficsim.core.perception.PerceivableContext;

  28. import nl.tudelft.simulation.dsol.SimRuntimeException;
  29. import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
  30. import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit;
  31. import nl.tudelft.simulation.language.d3.DirectedPoint;

  32. /**
  33.  * Implements the basic functionalities of any GTU: the ability to move on 3D-space according to a plan.
  34.  * <p>
  35.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  36.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  37.  * <p>
  38.  * @version $Revision: 6119 $, $LastChangedDate: 2020-01-24 02:44:57 +0100 (Fri, 24 Jan 2020) $, by $Author: averbraeck $,
  39.  *          initial version Oct 22, 2014 <br>
  40.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  41.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  42.  */
  43. public abstract class AbstractGTU extends EventProducer implements GTU
  44. {
  45.     /** */
  46.     private static final long serialVersionUID = 20140822L;

  47.     /** The id of the GTU. */
  48.     private final String id;

  49.     /** unique number of the GTU. */
  50.     private final int uniqueNumber;

  51.     /** the unique number counter. */
  52.     private static int staticUNIQUENUMBER = 0;

  53.     /** The type of GTU, e.g. TruckType, CarType, BusType. */
  54.     private final GTUType gtuType;

  55.     /** The simulator to schedule activities on. */
  56.     private final OTSSimulatorInterface simulator;

  57.     /** Model parameters. */
  58.     private Parameters parameters;

  59.     /** The maximum acceleration. */
  60.     private Acceleration maximumAcceleration;

  61.     /** The maximum deceleration, stored as a negative number. */
  62.     private Acceleration maximumDeceleration;

  63.     /**
  64.      * The odometer which measures how much distance have we covered between instantiation and the last completed operational
  65.      * plan. In order to get a complete odometer reading, the progress of the current plan execution has to be added to this
  66.      * value.
  67.      */
  68.     private Historical<Length> odometer;

  69.     /** The strategical planner that can instantiate tactical planners to determine mid-term decisions. */
  70.     private final Historical<StrategicalPlanner> strategicalPlanner;

  71.     /** The tactical planner that can generate an operational plan. */
  72.     private final Historical<TacticalPlanner<?, ?>> tacticalPlanner;

  73.     /** The current operational plan, which provides a short-term movement over time. */
  74.     protected final Historical<OperationalPlan> operationalPlan;

  75.     /** The next move event as scheduled on the simulator, can be used for interrupting the current move. */
  76.     private SimEvent<SimTimeDoubleUnit> nextMoveEvent;

  77.     /** The model in which this GTU is registered. */
  78.     private PerceivableContext perceivableContext;

  79.     /** Is this GTU destroyed? */
  80.     private boolean destroyed = false;

  81.     /** aligned or not. */
  82.     // TODO: should be indicated with a Parameter
  83.     public static boolean ALIGNED = true;

  84.     /** aligned schedule count. */
  85.     // TODO: can be removed after testing period
  86.     public static int ALIGN_COUNT = 0;

  87.     /** Cached speed time. */
  88.     private double cachedSpeedTime = Double.NaN;

  89.     /** Cached speed. */
  90.     private Speed cachedSpeed = null;

  91.     /** Cached acceleration time. */
  92.     private double cachedAccelerationTime = Double.NaN;

  93.     /** Cached acceleration. */
  94.     private Acceleration cachedAcceleration = null;

  95.     /** Parent GTU. */
  96.     private GTU parent = null;

  97.     /** Children GTU's. */
  98.     private Set<GTU> children = new LinkedHashSet<>();

  99.     /** Error handler. */
  100.     private GTUErrorHandler errorHandler = GTUErrorHandler.THROW;

  101.     /**
  102.      * @param id String; the id of the GTU
  103.      * @param gtuType GTUType; the type of GTU, e.g. TruckType, CarType, BusType
  104.      * @param simulator OTSSimulatorInterface; the simulator to schedule plan changes on
  105.      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
  106.      * @throws GTUException when the preconditions of the constructor are not met
  107.      */
  108.     @SuppressWarnings("checkstyle:parameternumber")
  109.     public AbstractGTU(final String id, final GTUType gtuType, final OTSSimulatorInterface simulator,
  110.             final PerceivableContext perceivableContext) throws GTUException
  111.     {
  112.         Throw.when(id == null, GTUException.class, "id is null");
  113.         Throw.when(gtuType == null, GTUException.class, "gtuType is null");
  114.         Throw.when(perceivableContext == null, GTUException.class, "perceivableContext is null for GTU with id %s", id);
  115.         Throw.when(perceivableContext.containsGtuId(id), GTUException.class,
  116.                 "GTU with id %s already registered in perceivableContext %s", id, perceivableContext.getId());
  117.         Throw.when(simulator == null, GTUException.class, "simulator is null for GTU with id %s", id);

  118.         HistoryManager historyManager = simulator.getReplication().getHistoryManager(simulator);
  119.         this.id = id;
  120.         this.uniqueNumber = ++staticUNIQUENUMBER;
  121.         this.gtuType = gtuType;
  122.         this.simulator = simulator;
  123.         this.odometer = new HistoricalValue<>(historyManager, Length.ZERO);
  124.         this.perceivableContext = perceivableContext;
  125.         this.perceivableContext.addGTU(this);
  126.         this.strategicalPlanner = new HistoricalValue<>(historyManager);
  127.         this.tacticalPlanner = new HistoricalValue<>(historyManager, null);
  128.         this.operationalPlan = new HistoricalValue<>(historyManager, null);
  129.     }

  130.     /**
  131.      * @param idGenerator IdGenerator; the generator that will produce a unique id of the GTU
  132.      * @param gtuType GTUType; the type of GTU, e.g. TruckType, CarType, BusType
  133.      * @param simulator OTSSimulatorInterface; the simulator to schedule plan changes on
  134.      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
  135.      * @throws GTUException when the preconditions of the constructor are not met
  136.      */
  137.     @SuppressWarnings("checkstyle:parameternumber")
  138.     public AbstractGTU(final IdGenerator idGenerator, final GTUType gtuType, final OTSSimulatorInterface simulator,
  139.             final PerceivableContext perceivableContext) throws GTUException
  140.     {
  141.         this(generateId(idGenerator), gtuType, simulator, perceivableContext);
  142.     }

  143.     /**
  144.      * Initialize the GTU at a location and speed, and give it a mission to fulfill through the strategical planner.
  145.      * @param strategicalPlanner StrategicalPlanner; the strategical planner responsible for the overall 'mission' of the GTU,
  146.      *            usually indicating where it needs to go. It operates by instantiating tactical planners to do the work.
  147.      * @param initialLocation DirectedPoint; the initial location (and direction) of the GTU
  148.      * @param initialSpeed Speed; the initial speed of the GTU
  149.      * @throws SimRuntimeException when scheduling after the first move fails
  150.      * @throws GTUException when the preconditions of the parameters are not met or when the construction of the original
  151.      *             waiting path fails
  152.      */
  153.     @SuppressWarnings({"checkstyle:hiddenfield", "hiding", "checkstyle:designforextension"})
  154.     public void init(final StrategicalPlanner strategicalPlanner, final DirectedPoint initialLocation, final Speed initialSpeed)
  155.             throws SimRuntimeException, GTUException
  156.     {
  157.         Throw.when(strategicalPlanner == null, GTUException.class, "strategicalPlanner is null for GTU with id %s", this.id);
  158.         Throw.whenNull(initialLocation, "Initial location of GTU cannot be null");
  159.         Throw.when(Double.isNaN(initialLocation.x) || Double.isNaN(initialLocation.y) || Double.isNaN(initialLocation.z),
  160.                 GTUException.class, "initialLocation %s invalid for GTU with id %s", initialLocation, this.id);
  161.         Throw.when(initialSpeed == null, GTUException.class, "initialSpeed is null for GTU with id %s", this.id);
  162.         Throw.when(!getId().equals(strategicalPlanner.getGtu().getId()), GTUException.class,
  163.                 "GTU %s is initialized with a strategical planner for GTU %s", getId(), strategicalPlanner.getGtu().getId());

  164.         this.strategicalPlanner.set(strategicalPlanner);
  165.         this.tacticalPlanner.set(strategicalPlanner.getTacticalPlanner());
  166.         Time now = this.simulator.getSimulatorTime();

  167.         fireTimedEvent(GTU.INIT_EVENT, new Object[] {getId(), initialLocation, getLength(), getWidth()}, now);

  168.         try
  169.         {
  170.             move(initialLocation);
  171.         }
  172.         catch (OperationalPlanException | NetworkException | ParameterException exception)
  173.         {
  174.             throw new GTUException("Failed to create OperationalPlan for GTU " + this.id, exception);
  175.         }
  176.     }

  177.     /**
  178.      * Generate an id, but check first that we have a valid IdGenerator.
  179.      * @param idGenerator IdGenerator; the generator that will produce a unique id of the GTU
  180.      * @return a (hopefully unique) Id of the GTU
  181.      * @throws GTUException when the idGenerator is null
  182.      */
  183.     private static String generateId(final IdGenerator idGenerator) throws GTUException
  184.     {
  185.         Throw.when(idGenerator == null, GTUException.class, "AbstractGTU.<init>: idGenerator is null");
  186.         return idGenerator.nextId();
  187.     }

  188.     /**
  189.      * Destructor. Don't forget to call with super.destroy() from any override to avoid memory leaks in the network.
  190.      */
  191.     @Override
  192.     @SuppressWarnings("checkstyle:designforextension")
  193.     public void destroy()
  194.     {
  195.         fireTimedEvent(GTU.DESTROY_EVENT, new Object[] {getId(), getLocation(), getOdometer()},
  196.                 this.simulator.getSimulatorTime());

  197.         // cancel the next move
  198.         if (this.nextMoveEvent != null)
  199.         {
  200.             this.simulator.cancelEvent(this.nextMoveEvent);
  201.             this.nextMoveEvent = null;
  202.         }

  203.         this.perceivableContext.removeGTU(this);
  204.         this.destroyed = true;
  205.     }

  206.     /**
  207.      * Move from the current location according to an operational plan to a location that will bring us nearer to reaching the
  208.      * location provided by the strategical planner. <br>
  209.      * This method can be overridden to carry out specific behavior during the execution of the plan (e.g., scheduling of
  210.      * triggers, entering or leaving lanes, etc.). Please bear in mind that the call to super.move() is essential, and that one
  211.      * has to take care to handle the situation that the plan gets interrupted.
  212.      * @param fromLocation DirectedPoint; the last known location (initial location, or end location of the previous operational
  213.      *            plan)
  214.      * @return boolean; whether an exception occurred
  215.      * @throws SimRuntimeException when scheduling of the next move fails
  216.      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
  217.      * @throws GTUException when there is a problem with the state of the GTU when planning a path
  218.      * @throws NetworkException in case of a problem with the network, e.g., a dead end where it is not expected
  219.      * @throws ParameterException in there is a parameter problem
  220.      */
  221.     @SuppressWarnings("checkstyle:designforextension")
  222.     protected boolean move(final DirectedPoint fromLocation)
  223.             throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
  224.     {
  225.         try
  226.         {
  227.             Time now = this.simulator.getSimulatorTime();

  228.             // Add the odometer distance from the currently running operational plan.
  229.             // Because a plan can be interrupted, we explicitly calculate the covered distance till 'now'
  230.             Length currentOdometer;
  231.             if (this.operationalPlan.get() != null)
  232.             {
  233.                 currentOdometer = this.odometer.get().plus(this.operationalPlan.get().getTraveledDistance(now));
  234.             }
  235.             else
  236.             {
  237.                 currentOdometer = this.odometer.get();
  238.             }

  239.             // Do we have an operational plan?
  240.             // TODO discuss when a new tactical planner may be needed
  241.             TacticalPlanner<?, ?> tactPlanner = this.tacticalPlanner.get();
  242.             if (tactPlanner == null)
  243.             {
  244.                 // Tell the strategical planner to provide a tactical planner
  245.                 tactPlanner = this.strategicalPlanner.get().getTacticalPlanner();
  246.                 this.tacticalPlanner.set(tactPlanner);
  247.             }
  248.             tactPlanner.getPerception().perceive();
  249.             OperationalPlan newOperationalPlan = tactPlanner.generateOperationalPlan(now, fromLocation);
  250.             this.operationalPlan.set(newOperationalPlan);
  251.             this.cachedSpeedTime = Double.NaN;
  252.             this.cachedAccelerationTime = Double.NaN;
  253.             this.odometer.set(currentOdometer);

  254.             // TODO allow alignment at different intervals, also different between GTU's within a single simulation
  255.             if (ALIGNED && newOperationalPlan.getTotalDuration().si == 0.5)
  256.             {
  257.                 // schedule the next move at exactly 0.5 seconds on the clock
  258.                 // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
  259.                 double tNext = Math.floor(2.0 * now.si + 1.0) / 2.0;
  260.                 DirectedPoint p = (tNext - now.si < 0.5) ? newOperationalPlan.getEndLocation()
  261.                         : newOperationalPlan.getLocation(new Duration(tNext - now.si, DurationUnit.SI));
  262.                 this.nextMoveEvent = new SimEvent<>(new SimTimeDoubleUnit(new Time(tNext, TimeUnit.DEFAULT)), this, this,
  263.                         "move", new Object[] {p});
  264.                 ALIGN_COUNT++;
  265.             }
  266.             else
  267.             {
  268.                 // schedule the next move at the end of the current operational plan
  269.                 // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
  270.                 this.nextMoveEvent = new SimEvent<>(new SimTimeDoubleUnit(now.plus(newOperationalPlan.getTotalDuration())),
  271.                         this, this, "move", new Object[] {newOperationalPlan.getEndLocation()});
  272.             }
  273.             this.simulator.scheduleEvent(this.nextMoveEvent);

  274.             fireTimedEvent(GTU.MOVE_EVENT, new Object[] {getId(), fromLocation, getSpeed(), getAcceleration(), getOdometer()},
  275.                     this.simulator.getSimulatorTime());

  276.             return false;
  277.         }
  278.         catch (Exception ex)
  279.         {
  280.             try
  281.             {
  282.                 this.errorHandler.handle(this, ex);
  283.             }
  284.             catch (Exception exception)
  285.             {
  286.                 throw new GTUException(exception);
  287.             }
  288.             return true;
  289.         }
  290.     }

  291.     /**
  292.      * Interrupt the move and ask for a new plan. This method can be overridden to carry out the bookkeeping needed when the
  293.      * current plan gets interrupted.
  294.      * @throws OperationalPlanException when there was a problem retrieving the location from the running plan
  295.      * @throws SimRuntimeException when scheduling of the next move fails
  296.      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
  297.      * @throws GTUException when there is a problem with the state of the GTU when planning a path
  298.      * @throws NetworkException in case of a problem with the network, e.g., unreachability of a certain point
  299.      * @throws ParameterException when there is a problem with a parameter
  300.      */
  301.     @SuppressWarnings("checkstyle:designforextension")
  302.     protected void interruptMove()
  303.             throws SimRuntimeException, OperationalPlanException, GTUException, NetworkException, ParameterException
  304.     {
  305.         this.simulator.cancelEvent(this.nextMoveEvent);
  306.         move(this.operationalPlan.get().getLocation(this.simulator.getSimulatorTime()));
  307.     }

  308.     /** {@inheritDoc} */
  309.     @Override
  310.     public final String getId()
  311.     {
  312.         return this.id;
  313.     }

  314.     /** {@inheritDoc} */
  315.     @SuppressWarnings("checkstyle:designforextension")
  316.     @Override
  317.     public GTUType getGTUType()
  318.     {
  319.         return this.gtuType;
  320.     }

  321.     /** {@inheritDoc} */
  322.     @Override
  323.     public final RelativePosition getReference()
  324.     {
  325.         return RelativePosition.REFERENCE_POSITION;
  326.     }

  327.     /** {@inheritDoc} */
  328.     @Override
  329.     public final OTSSimulatorInterface getSimulator()
  330.     {
  331.         return this.simulator;
  332.     }

  333.     /** {@inheritDoc} */
  334.     @Override
  335.     public final Parameters getParameters()
  336.     {
  337.         return this.parameters;
  338.     }

  339.     /** {@inheritDoc} */
  340.     @Override
  341.     public final void setParameters(final Parameters parameters)
  342.     {
  343.         this.parameters = parameters;
  344.     }

  345.     /** {@inheritDoc} */
  346.     @Override
  347.     public StrategicalPlanner getStrategicalPlanner()
  348.     {
  349.         return this.strategicalPlanner.get();
  350.     }

  351.     /** {@inheritDoc} */
  352.     @Override
  353.     public StrategicalPlanner getStrategicalPlanner(final Time time)
  354.     {
  355.         return this.strategicalPlanner.get(time);
  356.     }

  357.     /** {@inheritDoc} */
  358.     @Override
  359.     public final OperationalPlan getOperationalPlan()
  360.     {
  361.         return this.operationalPlan.get();
  362.     }

  363.     /** {@inheritDoc} */
  364.     @Override
  365.     public final OperationalPlan getOperationalPlan(final Time time)
  366.     {
  367.         return this.operationalPlan.get(time);
  368.     }

  369.     /** {@inheritDoc} */
  370.     @Override
  371.     public final Length getOdometer()
  372.     {
  373.         return getOdometer(this.simulator.getSimulatorTime());
  374.     }

  375.     /** {@inheritDoc} */
  376.     @Override
  377.     public final Length getOdometer(final Time time)
  378.     {
  379.         if (getOperationalPlan(time) == null)
  380.         {
  381.             return this.odometer.get(time);
  382.         }
  383.         try
  384.         {
  385.             return this.odometer.get(time).plus(getOperationalPlan(time).getTraveledDistance(time));
  386.         }
  387.         catch (OperationalPlanException ope)
  388.         {
  389.             return this.odometer.get(time);
  390.         }
  391.     }

  392.     /** {@inheritDoc} */
  393.     @Override
  394.     public final Speed getSpeed()
  395.     {
  396.         return getSpeed(this.simulator.getSimulatorTime());
  397.     }

  398.     /** {@inheritDoc} */
  399.     @Override
  400.     public final Speed getSpeed(final Time time)
  401.     {
  402.         if (this.cachedSpeedTime != time.si)
  403.         {
  404.             this.cachedSpeedTime = time.si;
  405.             OperationalPlan plan = getOperationalPlan(time);
  406.             if (plan == null)
  407.             {
  408.                 this.cachedSpeed = Speed.ZERO;
  409.             }
  410.             else if (time.si < plan.getStartTime().si)
  411.             {
  412.                 this.cachedSpeed = plan.getStartSpeed();
  413.             }
  414.             else if (time.si > plan.getEndTime().si)
  415.             {
  416.                 if (time.si - plan.getEndTime().si < 1e-6)
  417.                 {
  418.                     this.cachedSpeed = Try.assign(() -> plan.getSpeed(plan.getEndTime()),
  419.                             "getSpeed() could not derive a valid speed for the current operationalPlan");
  420.                 }
  421.                 else
  422.                 {
  423.                     throw new IllegalStateException("Requesting speed value beyond plan.");
  424.                 }
  425.             }
  426.             else
  427.             {
  428.                 this.cachedSpeed = Try.assign(() -> plan.getSpeed(time),
  429.                         "getSpeed() could not derive a valid speed for the current operationalPlan");
  430.             }
  431.         }
  432.         return this.cachedSpeed;
  433.     }

  434.     /** {@inheritDoc} */
  435.     @Override
  436.     public final Acceleration getAcceleration()
  437.     {
  438.         return getAcceleration(this.simulator.getSimulatorTime());
  439.     }

  440.     /** {@inheritDoc} */
  441.     @Override
  442.     public final Acceleration getAcceleration(final Time time)
  443.     {
  444.         if (this.cachedAccelerationTime != time.si)
  445.         {
  446.             this.cachedAccelerationTime = time.si;
  447.             OperationalPlan plan = getOperationalPlan(time);
  448.             if (plan == null)
  449.             {
  450.                 this.cachedAcceleration = Acceleration.ZERO;
  451.             }
  452.             else if (time.si < plan.getStartTime().si)
  453.             {
  454.                 this.cachedAcceleration =
  455.                         Try.assign(() -> plan.getAcceleration(plan.getStartTime()), "Exception obtaining acceleration.");
  456.             }
  457.             else if (time.si > plan.getEndTime().si)
  458.             {
  459.                 if (time.si - plan.getEndTime().si < 1e-6)
  460.                 {
  461.                     this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(plan.getEndTime()),
  462.                             "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
  463.                 }
  464.                 else
  465.                 {
  466.                     throw new IllegalStateException("Requesting acceleration value beyond plan.");
  467.                 }
  468.             }
  469.             else
  470.             {
  471.                 this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(time),
  472.                         "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
  473.             }
  474.         }
  475.         return this.cachedAcceleration;
  476.     }

  477.     /**
  478.      * @return maximumAcceleration
  479.      */
  480.     @Override
  481.     public final Acceleration getMaximumAcceleration()
  482.     {
  483.         return this.maximumAcceleration;
  484.     }

  485.     /**
  486.      * @param maximumAcceleration Acceleration; set maximumAcceleration
  487.      */
  488.     public final void setMaximumAcceleration(final Acceleration maximumAcceleration)
  489.     {
  490.         if (maximumAcceleration.le(Acceleration.ZERO))
  491.         {
  492.             throw new RuntimeException("Maximum acceleration of GTU " + this.id + " set to value <= 0");
  493.         }
  494.         this.maximumAcceleration = maximumAcceleration;
  495.     }

  496.     /**
  497.      * @return maximumDeceleration
  498.      */
  499.     @Override
  500.     public final Acceleration getMaximumDeceleration()
  501.     {
  502.         return this.maximumDeceleration;
  503.     }

  504.     /**
  505.      * @param maximumDeceleration Acceleration; set maximumDeceleration, stored as a negative number
  506.      */
  507.     public final void setMaximumDeceleration(final Acceleration maximumDeceleration)
  508.     {
  509.         if (maximumDeceleration.ge(Acceleration.ZERO))
  510.         {
  511.             throw new RuntimeException("Maximum deceleration of GTU " + this.id + " set to value >= 0");
  512.         }
  513.         this.maximumDeceleration = maximumDeceleration;
  514.     }

  515.     /** cache time. */
  516.     private Time cacheLocationTime = new Time(Double.NaN, TimeUnit.DEFAULT);

  517.     /** caced position at time. */
  518.     private DirectedPoint cacheLocation = null;

  519.     /** {@inheritDoc} */
  520.     @Override
  521.     @SuppressWarnings("checkstyle:designforextension")
  522.     public DirectedPoint getLocation()
  523.     {
  524.         if (this.operationalPlan.get() == null)
  525.         {
  526.             this.simulator.getLogger().always()
  527.                     .error("No operational plan for GTU " + this.id + " at t=" + this.getSimulator().getSimulatorTime());
  528.             return new DirectedPoint(0, 0, 0);
  529.         }
  530.         try
  531.         {
  532.             // cache
  533.             if (this.cacheLocationTime.si != this.simulator.getSimulatorTime().si)
  534.             {
  535.                 this.cacheLocationTime = this.simulator.getSimulatorTime();
  536.                 this.cacheLocation = this.operationalPlan.get().getLocation(this.cacheLocationTime);
  537.             }
  538.             return this.cacheLocation;
  539.         }
  540.         catch (OperationalPlanException exception)
  541.         {
  542.             return new DirectedPoint(0, 0, 0);
  543.         }
  544.     }

  545.     /** {@inheritDoc} */
  546.     @Override
  547.     public final boolean isDestroyed()
  548.     {
  549.         return this.destroyed;
  550.     }

  551.     /** {@inheritDoc} */
  552.     @Override
  553.     public PerceivableContext getPerceivableContext()
  554.     {
  555.         return this.perceivableContext;
  556.     }

  557.     /** {@inheritDoc} */
  558.     @Override
  559.     public void addGtu(final GTU gtu) throws GTUException
  560.     {
  561.         this.children.add(gtu);
  562.         gtu.setParent(this);
  563.     }

  564.     /** {@inheritDoc} */
  565.     @Override
  566.     public void removeGtu(final GTU gtu)
  567.     {
  568.         this.children.remove(gtu);
  569.         try
  570.         {
  571.             gtu.setParent(null);
  572.         }
  573.         catch (GTUException exception)
  574.         {
  575.             // cannot happen, setting null is always ok
  576.         }
  577.     }

  578.     /** {@inheritDoc} */
  579.     @Override
  580.     public void setParent(final GTU gtu) throws GTUException
  581.     {
  582.         Throw.when(gtu != null && this.parent != null, GTUException.class, "GTU %s already has a parent.", this);
  583.         this.parent = gtu;
  584.     }

  585.     /** {@inheritDoc} */
  586.     @Override
  587.     public GTU getParent()
  588.     {
  589.         return this.parent;
  590.     }

  591.     /** {@inheritDoc} */
  592.     @Override
  593.     public Set<GTU> getChildren()
  594.     {
  595.         return new LinkedHashSet<>(this.children); // safe copy
  596.     }

  597.     /**
  598.      * @return errorHandler.
  599.      */
  600.     protected GTUErrorHandler getErrorHandler()
  601.     {
  602.         return this.errorHandler;
  603.     }

  604.     /** {@inheritDoc} */
  605.     @Override
  606.     public void setErrorHandler(final GTUErrorHandler errorHandler)
  607.     {
  608.         this.errorHandler = errorHandler;
  609.     }

  610.     /** {@inheritDoc} */
  611.     @Override
  612.     public final Serializable getSourceId()
  613.     {
  614.         return this;  // TODO: see where the actual pointer to the GTU is needed
  615.     }

  616.     /**
  617.      * Note that destroying the next move event of the GTU can be dangerous!
  618.      * @return nextMoveEvent the next move event of the GTU, e.g. to cancel it from outside.
  619.      */
  620.     public final SimEvent<SimTimeDoubleUnit> getNextMoveEvent()
  621.     {
  622.         return this.nextMoveEvent;
  623.     }

  624.     /** {@inheritDoc} */
  625.     @Override
  626.     @SuppressWarnings("designforextension")
  627.     public int hashCode()
  628.     {
  629.         final int prime = 31;
  630.         int result = 1;
  631.         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
  632.         result = prime * result + this.uniqueNumber;
  633.         return result;
  634.     }

  635.     /** {@inheritDoc} */
  636.     @Override
  637.     @SuppressWarnings({"designforextension", "needbraces"})
  638.     public boolean equals(final Object obj)
  639.     {
  640.         if (this == obj)
  641.             return true;
  642.         if (obj == null)
  643.             return false;
  644.         if (getClass() != obj.getClass())
  645.             return false;
  646.         AbstractGTU other = (AbstractGTU) obj;
  647.         if (this.id == null)
  648.         {
  649.             if (other.id != null)
  650.                 return false;
  651.         }
  652.         else if (!this.id.equals(other.id))
  653.             return false;
  654.         if (this.uniqueNumber != other.uniqueNumber)
  655.             return false;
  656.         return true;
  657.     }

  658. }