Gtu.java

  1. package org.opentrafficsim.core.gtu;

  2. import java.io.Serializable;
  3. import java.util.ArrayList;
  4. import java.util.Arrays;
  5. import java.util.LinkedHashMap;
  6. import java.util.LinkedHashSet;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Set;

  10. import org.djunits.unit.DirectionUnit;
  11. import org.djunits.unit.DurationUnit;
  12. import org.djunits.unit.PositionUnit;
  13. import org.djunits.unit.TimeUnit;
  14. import org.djunits.value.vdouble.scalar.Acceleration;
  15. import org.djunits.value.vdouble.scalar.Direction;
  16. import org.djunits.value.vdouble.scalar.Duration;
  17. import org.djunits.value.vdouble.scalar.Length;
  18. import org.djunits.value.vdouble.scalar.Speed;
  19. import org.djunits.value.vdouble.scalar.Time;
  20. import org.djunits.value.vdouble.vector.PositionVector;
  21. import org.djutils.base.Identifiable;
  22. import org.djutils.draw.line.Polygon2d;
  23. import org.djutils.draw.point.OrientedPoint2d;
  24. import org.djutils.draw.point.Point2d;
  25. import org.djutils.event.EventType;
  26. import org.djutils.event.LocalEventProducer;
  27. import org.djutils.exceptions.Throw;
  28. import org.djutils.exceptions.Try;
  29. import org.djutils.immutablecollections.Immutable;
  30. import org.djutils.immutablecollections.ImmutableHashSet;
  31. import org.djutils.immutablecollections.ImmutableLinkedHashMap;
  32. import org.djutils.immutablecollections.ImmutableMap;
  33. import org.djutils.immutablecollections.ImmutableSet;
  34. import org.djutils.metadata.MetaData;
  35. import org.djutils.metadata.ObjectDescriptor;
  36. import org.opentrafficsim.base.HierarchicallyTyped;
  37. import org.opentrafficsim.base.geometry.BoundingRectangle;
  38. import org.opentrafficsim.base.geometry.OtsBounds2d;
  39. import org.opentrafficsim.base.geometry.OtsLocatable;
  40. import org.opentrafficsim.base.parameters.ParameterException;
  41. import org.opentrafficsim.base.parameters.Parameters;
  42. import org.opentrafficsim.core.DynamicSpatialObject;
  43. import org.opentrafficsim.core.animation.Drawable;
  44. import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
  45. import org.opentrafficsim.core.geometry.OtsGeometryException;
  46. import org.opentrafficsim.core.geometry.OtsLine2d;
  47. import org.opentrafficsim.core.gtu.RelativePosition.Type;
  48. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
  49. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
  50. import org.opentrafficsim.core.gtu.plan.strategical.StrategicalPlanner;
  51. import org.opentrafficsim.core.gtu.plan.tactical.TacticalPlanner;
  52. import org.opentrafficsim.core.network.NetworkException;
  53. import org.opentrafficsim.core.perception.Historical;
  54. import org.opentrafficsim.core.perception.HistoricalValue;
  55. import org.opentrafficsim.core.perception.HistoryManager;
  56. import org.opentrafficsim.core.perception.PerceivableContext;

  57. import nl.tudelft.simulation.dsol.SimRuntimeException;
  58. import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;

  59. /**
  60.  * Implements the basic functionalities of any GTU: the ability to move on 3D-space according to a plan.
  61.  * <p>
  62.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  63.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  64.  * </p>
  65.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  66.  * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  67.  */
  68. public class Gtu extends LocalEventProducer
  69.         implements HierarchicallyTyped<GtuType, Gtu>, DynamicSpatialObject, OtsLocatable, Serializable, Identifiable, Drawable
  70. {
  71.     /** */
  72.     private static final long serialVersionUID = 20140822L;

  73.     /** The id of the GTU. */
  74.     private final String id;

  75.     /** unique number of the GTU. */
  76.     private final int uniqueNumber;

  77.     /** the unique number counter. */
  78.     private static int staticUNIQUENUMBER = 0;

  79.     /** The type of GTU, e.g. TruckType, CarType, BusType. */
  80.     private final GtuType gtuType;

  81.     /** The simulator to schedule activities on. */
  82.     private final OtsSimulatorInterface simulator;

  83.     /** Model parameters. */
  84.     private Parameters parameters;

  85.     /** The maximum acceleration. */
  86.     private Acceleration maximumAcceleration;

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

  89.     /**
  90.      * The odometer which measures how much distance have we covered between instantiation and the last completed operational
  91.      * plan. In order to get a complete odometer reading, the progress of the current plan execution has to be added to this
  92.      * value.
  93.      */
  94.     private Historical<Length> odometer;

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

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

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

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

  103.     /** The model in which this GTU is registered. */
  104.     private PerceivableContext perceivableContext;

  105.     /** Is this GTU destroyed? */
  106.     private boolean destroyed = false;

  107.     /** aligned or not. */
  108.     // TODO: should be indicated with a Parameter
  109.     public static boolean ALIGNED = true;

  110.     /** aligned schedule count. */
  111.     // TODO: can be removed after testing period
  112.     public static int ALIGN_COUNT = 0;

  113.     /** Cached speed time. */
  114.     private double cachedSpeedTime = Double.NaN;

  115.     /** Cached speed. */
  116.     private Speed cachedSpeed = null;

  117.     /** Cached acceleration time. */
  118.     private double cachedAccelerationTime = Double.NaN;

  119.     /** Cached acceleration. */
  120.     private Acceleration cachedAcceleration = null;

  121.     /** Parent GTU. */
  122.     private Gtu parent = null;

  123.     /** Children GTU's. */
  124.     private Set<Gtu> children = new LinkedHashSet<>();

  125.     /** Error handler. */
  126.     private GtuErrorHandler errorHandler = GtuErrorHandler.THROW;

  127.     /** shape of the Gtu contour. */
  128.     private Polygon2d shape = null;

  129.     /** Sensing positions. */
  130.     private final Map<RelativePosition.Type, RelativePosition> relativePositions = new LinkedHashMap<>();

  131.     /** cached front. */
  132.     private final RelativePosition frontPos;

  133.     /** cached rear. */
  134.     private final RelativePosition rearPos;

  135.     /** contour points. */
  136.     private final Set<RelativePosition> contourPoints = new LinkedHashSet<>();

  137.     /** The maximum length of the GTU (parallel with driving direction). */
  138.     private final Length length;

  139.     /** The maximum width of the GTU (perpendicular to driving direction). */
  140.     private final Length width;

  141.     /** The maximum speed of the GTU (in the driving direction). */
  142.     private final Speed maximumSpeed;

  143.     /** Tags of the GTU, these are used for specific use cases of any sort. */
  144.     private final Map<String, String> tags = new LinkedHashMap<>();

  145.     /** Bounds. */
  146.     private OtsBounds2d bounds;

  147.     /**
  148.      * @param id String; the id of the GTU
  149.      * @param gtuType GtuType; the type of GTU, e.g. TruckType, CarType, BusType
  150.      * @param simulator OtsSimulatorInterface; the simulator to schedule plan changes on
  151.      * @param perceivableContext PerceivableContext; the perceivable context in which this GTU will be registered
  152.      * @param length Length; the maximum length of the GTU (parallel with driving direction)
  153.      * @param width Length; the maximum width of the GTU (perpendicular to driving direction)
  154.      * @param maximumSpeed Speed;the maximum speed of the GTU (in the driving direction)
  155.      * @param front Length; front distance relative to the reference position
  156.      * @param centerOfGravity Length; distance from the center of gravity to the reference position
  157.      * @throws GtuException when the preconditions of the constructor are not met
  158.      */
  159.     @SuppressWarnings("checkstyle:parameternumber")
  160.     public Gtu(final String id, final GtuType gtuType, final OtsSimulatorInterface simulator,
  161.             final PerceivableContext perceivableContext, final Length length, final Length width, final Speed maximumSpeed,
  162.             final Length front, final Length centerOfGravity) throws GtuException
  163.     {
  164.         Throw.when(id == null, GtuException.class, "id is null");
  165.         Throw.when(gtuType == null, GtuException.class, "gtuType is null");
  166.         Throw.when(perceivableContext == null, GtuException.class, "perceivableContext is null for GTU with id %s", id);
  167.         Throw.when(perceivableContext.containsGtuId(id), GtuException.class,
  168.                 "GTU with id %s already registered in perceivableContext %s", id, perceivableContext.getId());
  169.         Throw.when(simulator == null, GtuException.class, "simulator is null for GTU with id %s", id);

  170.         this.length = length;
  171.         this.width = width;
  172.         if (null == maximumSpeed)
  173.         {
  174.             throw new GtuException("maximumSpeed may not be null");
  175.         }
  176.         this.maximumSpeed = maximumSpeed;

  177.         HistoryManager historyManager = simulator.getReplication().getHistoryManager(simulator);
  178.         this.id = id;
  179.         this.uniqueNumber = ++staticUNIQUENUMBER;
  180.         this.gtuType = gtuType;
  181.         this.simulator = simulator;
  182.         this.odometer = new HistoricalValue<>(historyManager, Length.ZERO);
  183.         this.perceivableContext = perceivableContext;
  184.         this.perceivableContext.addGTU(this);
  185.         this.strategicalPlanner = new HistoricalValue<>(historyManager);
  186.         this.tacticalPlanner = new HistoricalValue<>(historyManager, null);
  187.         this.operationalPlan = new HistoricalValue<>(historyManager, null);

  188.         // sensor positions.
  189.         Length dy2 = width.times(0.5);
  190.         this.frontPos = new RelativePosition(front, Length.ZERO, Length.ZERO, RelativePosition.FRONT);
  191.         this.relativePositions.put(RelativePosition.FRONT, this.frontPos);
  192.         this.rearPos = new RelativePosition(front.minus(length), Length.ZERO, Length.ZERO, RelativePosition.REAR);
  193.         this.relativePositions.put(RelativePosition.REAR, this.rearPos);
  194.         this.relativePositions.put(RelativePosition.REFERENCE, RelativePosition.REFERENCE_POSITION);
  195.         this.relativePositions.put(RelativePosition.CENTER,
  196.                 new RelativePosition(Length.ZERO, Length.ZERO, Length.ZERO, RelativePosition.CENTER));
  197.         this.bounds = new BoundingRectangle(getRear().dx().si, getFront().dx().si, -dy2.si, dy2.si);

  198.         // Contour positions. For now, a rectangle with the four corners.
  199.         for (int i = -1; i <= 1; i += 2)
  200.         {
  201.             Length x = i < 0 ? front.minus(length) : front;
  202.             for (int j = -1; j <= 1; j += 2)
  203.             {
  204.                 this.contourPoints.add(new RelativePosition(x, dy2.times(j), Length.ZERO, RelativePosition.CONTOUR));
  205.             }
  206.         }
  207.     }

  208.     /**
  209.      * Initialize the GTU at a location and speed, and give it a mission to fulfill through the strategical planner.
  210.      * @param strategicalPlanner StrategicalPlanner; the strategical planner responsible for the overall 'mission' of the GTU,
  211.      *            usually indicating where it needs to go. It operates by instantiating tactical planners to do the work.
  212.      * @param initialLocation OrientedPoint2d; the initial location (and direction) of the GTU
  213.      * @param initialSpeed Speed; the initial speed of the GTU
  214.      * @throws SimRuntimeException when scheduling after the first move fails
  215.      * @throws GtuException when the preconditions of the parameters are not met or when the construction of the original
  216.      *             waiting path fails
  217.      */
  218.     @SuppressWarnings({"checkstyle:hiddenfield", "hiding", "checkstyle:designforextension"})
  219.     public void init(final StrategicalPlanner strategicalPlanner, final OrientedPoint2d initialLocation,
  220.             final Speed initialSpeed) throws SimRuntimeException, GtuException
  221.     {
  222.         Throw.when(strategicalPlanner == null, GtuException.class, "strategicalPlanner is null for GTU with id %s", this.id);
  223.         Throw.whenNull(initialLocation, "Initial location of GTU cannot be null");
  224.         Throw.when(Double.isNaN(initialLocation.x) || Double.isNaN(initialLocation.y), GtuException.class,
  225.                 "initialLocation %s invalid for GTU with id %s", initialLocation, this.id);
  226.         Throw.when(initialSpeed == null, GtuException.class, "initialSpeed is null for GTU with id %s", this.id);
  227.         Throw.when(!getId().equals(strategicalPlanner.getGtu().getId()), GtuException.class,
  228.                 "GTU %s is initialized with a strategical planner for GTU %s", getId(), strategicalPlanner.getGtu().getId());

  229.         this.strategicalPlanner.set(strategicalPlanner);
  230.         this.tacticalPlanner.set(strategicalPlanner.getTacticalPlanner());

  231.         try
  232.         {
  233.             move(initialLocation);
  234.         }
  235.         catch (OperationalPlanException | NetworkException | ParameterException exception)
  236.         {
  237.             throw new GtuException("Failed to create OperationalPlan for GTU " + this.id, exception);
  238.         }
  239.     }

  240.     /** @return the front position of the GTU, relative to its reference point. */
  241.     public final RelativePosition getFront()
  242.     {
  243.         return this.frontPos;
  244.     }

  245.     /** @return the rear position of the GTU, relative to its reference point. */
  246.     public final RelativePosition getRear()
  247.     {
  248.         return this.rearPos;
  249.     }

  250.     /** @return the center position of the GTU, relative to its reference point. */
  251.     public final RelativePosition getCenter()
  252.     {
  253.         return this.relativePositions.get(RelativePosition.CENTER);
  254.     }

  255.     /** @return the positions for this GTU, but not the contour points. */
  256.     public final ImmutableMap<Type, RelativePosition> getRelativePositions()
  257.     {
  258.         return new ImmutableLinkedHashMap<>(this.relativePositions, Immutable.WRAP);
  259.     }

  260.     /** @return the contour points of the GTU. */
  261.     public final ImmutableSet<RelativePosition> getContourPoints()
  262.     {
  263.         return new ImmutableHashSet<>(this.contourPoints, Immutable.WRAP);
  264.     }

  265.     /** @return the maximum length of the GTU (parallel with driving direction). */
  266.     public final Length getLength()
  267.     {
  268.         return this.length;
  269.     }

  270.     /** @return the maximum width of the GTU (perpendicular to driving direction). */
  271.     public final Length getWidth()
  272.     {
  273.         return this.width;
  274.     }

  275.     /** @return the maximum speed of the GTU, in the direction of movement. */
  276.     public final Speed getMaximumSpeed()
  277.     {
  278.         return this.maximumSpeed;
  279.     }

  280.     /** {@inheritDoc} */
  281.     @Override
  282.     public final OtsBounds2d getBounds()
  283.     {
  284.         // TODO: inconsistent with reference point, this is just half width/length
  285.         return this.bounds;
  286.     }

  287.     /**
  288.      * Destructor. Don't forget to call with super.destroy() from any override to avoid memory leaks in the network.
  289.      */
  290.     @SuppressWarnings("checkstyle:designforextension")
  291.     public void destroy()
  292.     {
  293.         OrientedPoint2d location = getLocation();
  294.         fireTimedEvent(Gtu.DESTROY_EVENT,
  295.                 new Object[] {getId(), new PositionVector(new double[] {location.x, location.y}, PositionUnit.METER),
  296.                         new Direction(location.getDirZ(), DirectionUnit.EAST_RADIAN), getOdometer()},
  297.                 this.simulator.getSimulatorTime());

  298.         // cancel the next move
  299.         if (this.nextMoveEvent != null)
  300.         {
  301.             this.simulator.cancelEvent(this.nextMoveEvent);
  302.             this.nextMoveEvent = null;
  303.         }

  304.         this.perceivableContext.removeGTU(this);
  305.         this.destroyed = true;
  306.     }

  307.     /**
  308.      * Move from the current location according to an operational plan to a location that will bring us nearer to reaching the
  309.      * location provided by the strategical planner. <br>
  310.      * This method can be overridden to carry out specific behavior during the execution of the plan (e.g., scheduling of
  311.      * triggers, entering or leaving lanes, etc.). Please bear in mind that the call to super.move() is essential, and that one
  312.      * has to take care to handle the situation that the plan gets interrupted.
  313.      * @param fromLocation OrientedPoint2d; the last known location (initial location, or end location of the previous
  314.      *            operational plan)
  315.      * @return boolean; whether an exception occurred
  316.      * @throws SimRuntimeException when scheduling of the next move fails
  317.      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
  318.      * @throws GtuException when there is a problem with the state of the GTU when planning a path
  319.      * @throws NetworkException in case of a problem with the network, e.g., a dead end where it is not expected
  320.      * @throws ParameterException in there is a parameter problem
  321.      */
  322.     @SuppressWarnings("checkstyle:designforextension")
  323.     protected boolean move(final OrientedPoint2d fromLocation)
  324.             throws SimRuntimeException, OperationalPlanException, GtuException, NetworkException, ParameterException
  325.     {
  326.         try
  327.         {
  328.             Time now = this.simulator.getSimulatorAbsTime();

  329.             // Add the odometer distance from the currently running operational plan.
  330.             // Because a plan can be interrupted, we explicitly calculate the covered distance till 'now'
  331.             Length currentOdometer;
  332.             if (this.operationalPlan.get() != null)
  333.             {
  334.                 currentOdometer = this.odometer.get().plus(this.operationalPlan.get().getTraveledDistance(now));
  335.             }
  336.             else
  337.             {
  338.                 currentOdometer = this.odometer.get();
  339.             }

  340.             // Do we have an operational plan?
  341.             // TODO discuss when a new tactical planner may be needed
  342.             TacticalPlanner<?, ?> tactPlanner = this.tacticalPlanner.get();
  343.             if (tactPlanner == null)
  344.             {
  345.                 // Tell the strategical planner to provide a tactical planner
  346.                 tactPlanner = this.strategicalPlanner.get().getTacticalPlanner();
  347.                 this.tacticalPlanner.set(tactPlanner);
  348.             }
  349.             synchronized (this)
  350.             {
  351.                 tactPlanner.getPerception().perceive();
  352.             }
  353.             OperationalPlan newOperationalPlan = tactPlanner.generateOperationalPlan(now, fromLocation);
  354.             synchronized (this)
  355.             {
  356.                 this.operationalPlan.set(newOperationalPlan);
  357.                 this.cachedSpeedTime = Double.NaN;
  358.                 this.cachedAccelerationTime = Double.NaN;
  359.                 this.odometer.set(currentOdometer);
  360.             }

  361.             // TODO allow alignment at different intervals, also different between GTU's within a single simulation
  362.             if (ALIGNED && newOperationalPlan.getTotalDuration().si == 0.5)
  363.             {
  364.                 // schedule the next move at exactly 0.5 seconds on the clock
  365.                 // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
  366.                 double tNext = Math.floor(2.0 * now.si + 1.0) / 2.0;
  367.                 OrientedPoint2d p = (tNext - now.si < 0.5) ? newOperationalPlan.getEndLocation()
  368.                         : newOperationalPlan.getLocation(new Duration(tNext - now.si, DurationUnit.SI));
  369.                 this.nextMoveEvent =
  370.                         new SimEvent<Duration>(new Duration(tNext - getSimulator().getStartTimeAbs().si, DurationUnit.SI), this,
  371.                                 "move", new Object[] {p});
  372.                 ALIGN_COUNT++;
  373.             }
  374.             else
  375.             {
  376.                 // schedule the next move at the end of the current operational plan
  377.                 // store the event, so it can be cancelled in case the plan has to be interrupted and changed halfway
  378.                 this.nextMoveEvent =
  379.                         new SimEvent<>(now.plus(newOperationalPlan.getTotalDuration()).minus(getSimulator().getStartTimeAbs()),
  380.                                 this, "move", new Object[] {newOperationalPlan.getEndLocation()});
  381.             }
  382.             this.simulator.scheduleEvent(this.nextMoveEvent);
  383.             fireTimedEvent(Gtu.MOVE_EVENT,
  384.                     new Object[] {getId(),
  385.                             new PositionVector(new double[] {fromLocation.x, fromLocation.y}, PositionUnit.METER),
  386.                             new Direction(fromLocation.getDirZ(), DirectionUnit.EAST_RADIAN), getSpeed(), getAcceleration(),
  387.                             getOdometer()},
  388.                     this.simulator.getSimulatorTime());

  389.             return false;
  390.         }
  391.         catch (Exception ex)
  392.         {
  393.             try
  394.             {
  395.                 this.errorHandler.handle(this, ex);
  396.             }
  397.             catch (Exception exception)
  398.             {
  399.                 throw new GtuException(exception);
  400.             }
  401.             return true;
  402.         }
  403.     }

  404.     /**
  405.      * Interrupt the move and ask for a new plan. This method can be overridden to carry out the bookkeeping needed when the
  406.      * current plan gets interrupted.
  407.      * @throws OperationalPlanException when there was a problem retrieving the location from the running plan
  408.      * @throws SimRuntimeException when scheduling of the next move fails
  409.      * @throws OperationalPlanException when there is a problem creating a good path for the GTU
  410.      * @throws GtuException when there is a problem with the state of the GTU when planning a path
  411.      * @throws NetworkException in case of a problem with the network, e.g., unreachability of a certain point
  412.      * @throws ParameterException when there is a problem with a parameter
  413.      */
  414.     @SuppressWarnings("checkstyle:designforextension")
  415.     protected void interruptMove()
  416.             throws SimRuntimeException, OperationalPlanException, GtuException, NetworkException, ParameterException
  417.     {
  418.         this.simulator.cancelEvent(this.nextMoveEvent);
  419.         move(this.operationalPlan.get().getLocation(this.simulator.getSimulatorAbsTime()));
  420.     }

  421.     /** @return the id of the GTU */
  422.     @Override
  423.     public final String getId()
  424.     {
  425.         return this.id;
  426.     }

  427.     /**
  428.      * Sets a tag, these are used for specific use cases of any sort.
  429.      * @param tag String; name of the tag.
  430.      * @param value String; value of the tag.
  431.      */
  432.     public void setTag(final String tag, final String value)
  433.     {
  434.         this.tags.put(tag, value);
  435.     }

  436.     /**
  437.      * Returns the value for the given tag, these are used for specific use cases of any sort.
  438.      * @param tag String; name of the tag.
  439.      * @return String; value of the tag, or {@code null} if it is not given to the GTU.
  440.      */
  441.     public String getTag(final String tag)
  442.     {
  443.         return this.tags.get(tag);
  444.     }

  445.     /** {@inheritDoc} */
  446.     @SuppressWarnings("checkstyle:designforextension")
  447.     @Override
  448.     public GtuType getType()
  449.     {
  450.         return this.gtuType;
  451.     }

  452.     /** @return the reference position of the GTU, by definition (0, 0, 0). */
  453.     public final RelativePosition getReference()
  454.     {
  455.         return RelativePosition.REFERENCE_POSITION;
  456.     }

  457.     /** @return the simulator of the GTU. */
  458.     public final OtsSimulatorInterface getSimulator()
  459.     {
  460.         return this.simulator;
  461.     }

  462.     /** @return Parameters. */
  463.     public final Parameters getParameters()
  464.     {
  465.         return this.parameters;
  466.     }

  467.     /** @param parameters Parameters; parameters */
  468.     public final void setParameters(final Parameters parameters)
  469.     {
  470.         this.parameters = parameters;
  471.     }

  472.     /**
  473.      * @return StrategicalPlanner; the planner responsible for the overall 'mission' of the GTU, usually indicating where it
  474.      *         needs to go. It operates by instantiating tactical planners to do the work.
  475.      */
  476.     public StrategicalPlanner getStrategicalPlanner()
  477.     {
  478.         return this.strategicalPlanner.get();
  479.     }

  480.     /**
  481.      * @param time Time; time to obtain the strategical planner at
  482.      * @return StrategicalPlanner; the planner responsible for the overall 'mission' of the GTU, usually indicating where it
  483.      *         needs to go. It operates by instantiating tactical planners to do the work.
  484.      */
  485.     public StrategicalPlanner getStrategicalPlanner(final Time time)
  486.     {
  487.         return this.strategicalPlanner.get(time);
  488.     }

  489.     /** @return TacticalPlanner; the current tactical planner that can generate an operational plan */
  490.     public TacticalPlanner<?, ?> getTacticalPlanner()
  491.     {
  492.         return getStrategicalPlanner().getTacticalPlanner();
  493.     }

  494.     /**
  495.      * @param time Time; time to obtain the tactical planner at
  496.      * @return TacticalPlanner; the tactical planner that can generate an operational plan at the given time
  497.      */
  498.     public TacticalPlanner<?, ?> getTacticalPlanner(final Time time)
  499.     {
  500.         return getStrategicalPlanner(time).getTacticalPlanner(time);
  501.     }

  502.     /** @return the current operational plan for the GTU */
  503.     public final OperationalPlan getOperationalPlan()
  504.     {
  505.         return this.operationalPlan.get();
  506.     }

  507.     /**
  508.      * @param time Time; time to obtain the operational plan at
  509.      * @return the operational plan for the GTU at the given time.
  510.      */
  511.     public final OperationalPlan getOperationalPlan(final Time time)
  512.     {
  513.         return this.operationalPlan.get(time);
  514.     }

  515.     /**
  516.      * Set the operational plan. This method is for sub classes.
  517.      * @param operationalPlan OperationalPlan; operational plan.
  518.      */
  519.     protected void setOperationalPlan(final OperationalPlan operationalPlan)
  520.     {
  521.         this.operationalPlan.set(operationalPlan);
  522.     }

  523.     /**
  524.      * @return Length; the current odometer value.
  525.      */
  526.     public final Length getOdometer()
  527.     {
  528.         return getOdometer(this.simulator.getSimulatorAbsTime());
  529.     }

  530.     /**
  531.      * @param time Time; time to obtain the odometer at
  532.      * @return Length; the odometer value at given time.
  533.      */
  534.     public final Length getOdometer(final Time time)
  535.     {
  536.         synchronized (this)
  537.         {
  538.             if (getOperationalPlan(time) == null)
  539.             {
  540.                 return this.odometer.get(time);
  541.             }
  542.             try
  543.             {
  544.                 return this.odometer.get(time).plus(getOperationalPlan(time).getTraveledDistance(time));
  545.             }
  546.             catch (OperationalPlanException ope)
  547.             {
  548.                 return this.odometer.get(time);
  549.             }
  550.         }
  551.     }

  552.     /** @return the current speed of the GTU, along the direction of movement. */
  553.     public final Speed getSpeed()
  554.     {
  555.         synchronized (this)
  556.         {
  557.             return getSpeed(this.simulator.getSimulatorAbsTime());
  558.         }
  559.     }

  560.     /**
  561.      * @param time Time; time at which to obtain the speed
  562.      * @return the current speed of the GTU, along the direction of movement.
  563.      */
  564.     public final Speed getSpeed(final Time time)
  565.     {
  566.         synchronized (this)
  567.         {
  568.             if (this.cachedSpeedTime != time.si)
  569.             {
  570.                 // Invalidate everything
  571.                 this.cachedSpeedTime = Double.NaN;
  572.                 this.cachedSpeed = null;
  573.                 OperationalPlan plan = getOperationalPlan(time);
  574.                 if (plan == null)
  575.                 {
  576.                     this.cachedSpeed = Speed.ZERO;
  577.                 }
  578.                 else if (time.si < plan.getStartTime().si)
  579.                 {
  580.                     this.cachedSpeed = plan.getStartSpeed();
  581.                 }
  582.                 else if (time.si > plan.getEndTime().si)
  583.                 {
  584.                     if (time.si - plan.getEndTime().si < 1e-6)
  585.                     {
  586.                         this.cachedSpeed = Try.assign(() -> plan.getSpeed(plan.getEndTime()),
  587.                                 "getSpeed() could not derive a valid speed for the current operationalPlan");
  588.                     }
  589.                     else
  590.                     {
  591.                         throw new IllegalStateException("Requesting speed value beyond plan.");
  592.                     }
  593.                 }
  594.                 else
  595.                 {
  596.                     this.cachedSpeed = Try.assign(() -> plan.getSpeed(time),
  597.                             "getSpeed() could not derive a valid speed for the current operationalPlan");
  598.                 }
  599.                 this.cachedSpeedTime = time.si; // Do this last
  600.             }
  601.             return this.cachedSpeed;
  602.         }
  603.     }

  604.     /** @return the current acceleration of the GTU, along the direction of movement. */
  605.     public final Acceleration getAcceleration()
  606.     {
  607.         synchronized (this)
  608.         {
  609.             return getAcceleration(this.simulator.getSimulatorAbsTime());
  610.         }
  611.     }

  612.     /**
  613.      * @param time Time; time at which to obtain the acceleration
  614.      * @return the current acceleration of the GTU, along the direction of movement.
  615.      */
  616.     public final Acceleration getAcceleration(final Time time)
  617.     {
  618.         synchronized (this)
  619.         {
  620.             if (this.cachedAccelerationTime != time.si)
  621.             {
  622.                 // Invalidate everything
  623.                 this.cachedAccelerationTime = Double.NaN;
  624.                 this.cachedAcceleration = null;
  625.                 OperationalPlan plan = getOperationalPlan(time);
  626.                 if (plan == null)
  627.                 {
  628.                     this.cachedAcceleration = Acceleration.ZERO;
  629.                 }
  630.                 else if (time.si < plan.getStartTime().si)
  631.                 {
  632.                     this.cachedAcceleration =
  633.                             Try.assign(() -> plan.getAcceleration(plan.getStartTime()), "Exception obtaining acceleration.");
  634.                 }
  635.                 else if (time.si > plan.getEndTime().si)
  636.                 {
  637.                     if (time.si - plan.getEndTime().si < 1e-6)
  638.                     {
  639.                         this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(plan.getEndTime()),
  640.                                 "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
  641.                     }
  642.                     else
  643.                     {
  644.                         throw new IllegalStateException("Requesting acceleration value beyond plan.");
  645.                     }
  646.                 }
  647.                 else
  648.                 {
  649.                     this.cachedAcceleration = Try.assign(() -> plan.getAcceleration(time),
  650.                             "getAcceleration() could not derive a valid acceleration for the current operationalPlan");
  651.                 }
  652.                 this.cachedAccelerationTime = time.si;
  653.             }
  654.             return this.cachedAcceleration;
  655.         }
  656.     }

  657.     /**
  658.      * @return maximumAcceleration
  659.      */
  660.     public final Acceleration getMaximumAcceleration()
  661.     {
  662.         return this.maximumAcceleration;
  663.     }

  664.     /**
  665.      * @param maximumAcceleration Acceleration; set maximumAcceleration
  666.      */
  667.     public final void setMaximumAcceleration(final Acceleration maximumAcceleration)
  668.     {
  669.         if (maximumAcceleration.le(Acceleration.ZERO))
  670.         {
  671.             throw new RuntimeException("Maximum acceleration of GTU " + this.id + " set to value <= 0");
  672.         }
  673.         this.maximumAcceleration = maximumAcceleration;
  674.     }

  675.     /**
  676.      * @return maximumDeceleration
  677.      */
  678.     public final Acceleration getMaximumDeceleration()
  679.     {
  680.         return this.maximumDeceleration;
  681.     }

  682.     /**
  683.      * Set the maximum deceleration.
  684.      * @param maximumDeceleration Acceleration; set maximumDeceleration, must be a negative number
  685.      */
  686.     public final void setMaximumDeceleration(final Acceleration maximumDeceleration)
  687.     {
  688.         if (maximumDeceleration.ge(Acceleration.ZERO))
  689.         {
  690.             throw new RuntimeException("Cannot set maximum deceleration of GTU " + this.id + " to " + maximumDeceleration
  691.                     + " (value must be negative)");
  692.         }
  693.         this.maximumDeceleration = maximumDeceleration;
  694.     }

  695.     /** Cache location time. */
  696.     private Time cacheLocationTime = new Time(Double.NaN, TimeUnit.DEFAULT);

  697.     /** Cached location at that time. */
  698.     private OrientedPoint2d cacheLocation = null;

  699.     /** {@inheritDoc} */
  700.     @Override
  701.     @SuppressWarnings("checkstyle:designforextension")
  702.     public OrientedPoint2d getLocation()
  703.     {
  704.         synchronized (this)
  705.         {
  706.             if (this.operationalPlan.get() == null)
  707.             {
  708.                 this.simulator.getLogger().always()
  709.                         .error("No operational plan for GTU " + this.id + " at t=" + this.getSimulator().getSimulatorTime());
  710.                 return new OrientedPoint2d(0, 0, 0); // Do not cache it
  711.             }
  712.             try
  713.             {
  714.                 // cache
  715.                 Time locationTime = this.simulator.getSimulatorAbsTime();
  716.                 if (null == this.cacheLocationTime || this.cacheLocationTime.si != locationTime.si)
  717.                 {
  718.                     this.cacheLocationTime = null;
  719.                     this.cacheLocation = this.operationalPlan.get().getLocation(locationTime);
  720.                     this.cacheLocationTime = locationTime;
  721.                 }
  722.                 return this.cacheLocation;
  723.             }
  724.             catch (OperationalPlanException exception)
  725.             {
  726.                 return new OrientedPoint2d(0, 0, 0);
  727.             }
  728.         }
  729.     }

  730.     /**
  731.      * Return the shape of a dynamic object at time 'time'. Note that the getShape() method without a time returns the Minkowski
  732.      * sum of all shapes of the spatial object for a validity time window, e.g., a contour that describes all locations of a GTU
  733.      * for the next time step, i.e., the contour of the GTU belonging to the next operational plan.
  734.      * @param time Time; the time for which we want the shape
  735.      * @return OtsShape; the shape of the object at time 'time'
  736.      */
  737.     @Override
  738.     public Polygon2d getShape(final Time time)
  739.     {
  740.         try
  741.         {
  742.             if (this.shape == null)
  743.             {
  744.                 double w = getWidth().si;
  745.                 double l = getLength().si;
  746.                 this.shape = new Polygon2d(new Point2d(-0.5 * l, -0.5 * w), new Point2d(-0.5 * l, 0.5 * w),
  747.                         new Point2d(0.5 * l, 0.5 * w), new Point2d(0.5 * l, -0.5 * w));
  748.             }
  749.             Polygon2d s = transformShape(this.shape, this.operationalPlan.get(time).getLocation(time));
  750.             System.out.println("gtu " + getId() + ", shape(t)=" + s);
  751.             return s;
  752.         }
  753.         catch (OtsGeometryException | OperationalPlanException exception)
  754.         {
  755.             throw new RuntimeException(exception);
  756.         }
  757.     }

  758.     /**
  759.      * Return the shape of the GTU for the validity time of the operational plan. Note that this method without a time returns
  760.      * the Minkowski sum of all shapes of the spatial object for a validity time window, e.g., a contour that describes all
  761.      * locations of a GTU for the next time step, i.e., the contour of the GTU belonging to the next operational plan.
  762.      * @return OtsShape; the shape of the object over the validity of the operational plan
  763.      */
  764.     @Override
  765.     public Polygon2d getShape()
  766.     {
  767.         try
  768.         {
  769.             // TODO: the actual contour of the GTU has to be moved over the path
  770.             OtsLine2d path = this.operationalPlan.get().getPath();
  771.             // part of the Gtu length has to be added before the start and after the end of the path.
  772.             // we assume the reference point is within the contour of the Gtu.
  773.             double rear = Math.max(0.0, getReference().dx().si - getRear().dx().si);
  774.             double front = path.getLength().si + Math.max(0.0, getFront().dx().si - getReference().dx().si);
  775.             Point2d p0 = path.getLocationExtendedSI(-rear);
  776.             Point2d pn = path.getLocationExtendedSI(front);
  777.             List<Point2d> pList = new ArrayList<>(Arrays.asList(path.getPoints()));
  778.             pList.add(0, p0);
  779.             pList.add(pn);
  780.             OtsLine2d extendedPath = new OtsLine2d(pList);
  781.             List<Point2d> swath = new ArrayList<>();
  782.             swath.addAll(Arrays.asList(extendedPath.offsetLine(getWidth().si / 2.0).getPoints()));
  783.             swath.addAll(Arrays.asList(extendedPath.offsetLine(-getWidth().si / 2.0).reverse().getPoints()));
  784.             Polygon2d s = new Polygon2d(swath);
  785.             // System.out.println("gtu " + getId() + ", w=" + getWidth() + ", path="
  786.             // + this.operationalPlan.get().getPath().toString() + ", shape=" + s);
  787.             return s;
  788.         }
  789.         catch (Exception e)
  790.         {
  791.             throw new RuntimeException(e);
  792.         }
  793.     }

  794.     /**
  795.      * Returns whether the GTU is destroyed.
  796.      * @return whether the GTU is destroyed
  797.      */
  798.     public final boolean isDestroyed()
  799.     {
  800.         return this.destroyed;
  801.     }

  802.     /** @return the context to which the GTU belongs */
  803.     public PerceivableContext getPerceivableContext()
  804.     {
  805.         return this.perceivableContext;
  806.     }

  807.     /**
  808.      * Adds the provided GTU to this GTU, meaning it moves with this GTU.
  809.      * @param gtu Gtu; gtu to enter this GTU
  810.      * @throws GtuException if the gtu already has a parent
  811.      */
  812.     public void addGtu(final Gtu gtu) throws GtuException
  813.     {
  814.         this.children.add(gtu);
  815.         gtu.setParent(this);
  816.     }

  817.     /**
  818.      * Removes the provided GTU from this GTU, meaning it no longer moves with this GTU.
  819.      * @param gtu Gtu; gtu to exit this GTU
  820.      */
  821.     public void removeGtu(final Gtu gtu)
  822.     {
  823.         this.children.remove(gtu);
  824.         try
  825.         {
  826.             gtu.setParent(null);
  827.         }
  828.         catch (GtuException exception)
  829.         {
  830.             // cannot happen, setting null is always ok
  831.         }
  832.     }

  833.     /**
  834.      * Set the parent GTU.
  835.      * @param gtu Gtu; parent GTU, may be {@code null}
  836.      * @throws GtuException if the gtu already has a parent
  837.      */
  838.     public void setParent(final Gtu gtu) throws GtuException
  839.     {
  840.         Throw.when(gtu != null && this.parent != null, GtuException.class, "GTU %s already has a parent.", this);
  841.         this.parent = gtu;
  842.     }

  843.     /**
  844.      * Returns the parent GTU, or {@code null} if this GTU has no parent.
  845.      * @return Gtu; parent GTU, or {@code null} if this GTU has no parent
  846.      */
  847.     public Gtu getParent()
  848.     {
  849.         return this.parent;
  850.     }

  851.     /**
  852.      * Returns the children GTU's.
  853.      * @return Set&lt;GTU&gt;; children GTU's
  854.      */
  855.     public Set<Gtu> getChildren()
  856.     {
  857.         return new LinkedHashSet<>(this.children); // safe copy
  858.     }

  859.     /**
  860.      * @return errorHandler.
  861.      */
  862.     protected GtuErrorHandler getErrorHandler()
  863.     {
  864.         return this.errorHandler;
  865.     }

  866.     /**
  867.      * Sets the error handler.
  868.      * @param errorHandler GTUErrorHandler; error handler
  869.      */
  870.     public void setErrorHandler(final GtuErrorHandler errorHandler)
  871.     {
  872.         this.errorHandler = errorHandler;
  873.     }

  874.     /**
  875.      * Note that destroying the next move event of the GTU can be dangerous!
  876.      * @return nextMoveEvent the next move event of the GTU, e.g. to cancel it from outside.
  877.      */
  878.     public final SimEvent<Duration> getNextMoveEvent()
  879.     {
  880.         return this.nextMoveEvent;
  881.     }

  882.     /** {@inheritDoc} */
  883.     @Override
  884.     @SuppressWarnings("designforextension")
  885.     public int hashCode()
  886.     {
  887.         final int prime = 31;
  888.         int result = 1;
  889.         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
  890.         result = prime * result + this.uniqueNumber;
  891.         return result;
  892.     }

  893.     /** {@inheritDoc} */
  894.     @Override
  895.     @SuppressWarnings({"designforextension", "needbraces"})
  896.     public boolean equals(final Object obj)
  897.     {
  898.         if (this == obj)
  899.             return true;
  900.         if (obj == null)
  901.             return false;
  902.         if (getClass() != obj.getClass())
  903.             return false;
  904.         Gtu other = (Gtu) obj;
  905.         if (this.id == null)
  906.         {
  907.             if (other.id != null)
  908.                 return false;
  909.         }
  910.         else if (!this.id.equals(other.id))
  911.             return false;
  912.         if (this.uniqueNumber != other.uniqueNumber)
  913.             return false;
  914.         return true;
  915.     }

  916.     /**
  917.      * The event type for pub/sub indicating a move. <br>
  918.      * Payload: [String id, DirectedPoint position, Speed speed, Acceleration acceleration, Length odometer]
  919.      */
  920.     public static EventType MOVE_EVENT = new EventType("GTU.MOVE",
  921.             new MetaData("GTU move", "GTU id, position, speed, acceleration, odometer",
  922.                     new ObjectDescriptor[] {new ObjectDescriptor("Id", "GTU Id", String.class),
  923.                             new ObjectDescriptor("position", "position", PositionVector.class),
  924.                             new ObjectDescriptor("direction", "direction", Direction.class),
  925.                             new ObjectDescriptor("speed", "speed", Speed.class),
  926.                             new ObjectDescriptor("acceleration", "acceleration", Acceleration.class),
  927.                             new ObjectDescriptor("Odometer", "Total distance travelled since incarnation", Length.class)}));

  928.     /**
  929.      * The event type for pub/sub indicating destruction of the GTU. <br>
  930.      * Payload: [String id, DirectedPoint lastPosition, Length odometer]
  931.      */
  932.     public static EventType DESTROY_EVENT = new EventType("GTU.DESTROY",
  933.             new MetaData("GTU destroy", "GTU id, final position, final odometer",
  934.                     new ObjectDescriptor[] {new ObjectDescriptor("Id", "GTU Id", String.class),
  935.                             new ObjectDescriptor("position", "position", PositionVector.class),
  936.                             new ObjectDescriptor("direction", "direction", Direction.class),
  937.                             new ObjectDescriptor("Odometer", "Total distance travelled since incarnation", Length.class)}));

  938. }