PerceivedGtu.java

package org.opentrafficsim.road.gtu.lane.perception.object;

import java.util.Optional;

import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.djutils.exceptions.Throw;
import org.djutils.exceptions.Try;
import org.opentrafficsim.base.parameters.ParameterSet;
import org.opentrafficsim.base.parameters.Parameters;
import org.opentrafficsim.core.gtu.GtuType;
import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
import org.opentrafficsim.core.network.LateralDirectionality;
import org.opentrafficsim.core.network.route.Route;
import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
import org.opentrafficsim.road.gtu.lane.perception.GtuTypeAssumptions;
import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.LmrsParameters;
import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
import org.opentrafficsim.road.network.speed.SpeedLimitTypes;

/**
 * Interface for perceived surrounding GTU's, adding signals, maneuver and behavioral information.
 * <p>
 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
 * </p>
 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
 * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
 */
public interface PerceivedGtu extends PerceivedObject
{

    /**
     * Returns the GTU type.
     * @return gtuType
     */
    GtuType getGtuType();

    /**
     * Returns the width of the GTU.
     * @return width of the GTU
     */
    Length getWidth();

    /**
     * Returns information on the signals. This includes indicators and braking lights.
     * @return information on the signals
     */
    Signals getSignals();

    /**
     * Returns information on the maneuver. This includes lane changing and lateral deviation.
     * @return information on the maneuver
     */
    Maneuver getManeuver();

    /**
     * Returns information on the behavior. This includes the car-following model, parameters, desired speed, route, lane change
     * desire and social pressure.
     * @return information on the behavior
     */
    Behavior getBehavior();

    /**
     * Signal information.
     */
    interface Signals
    {
        /** Instance with no signals. */
        Signals NONE = new Record(TurnIndicatorStatus.NONE, false);

        /**
         * Returns indicator status.
         * @param lat direction of indicator.
         * @return indicator status
         * @throws IllegalArgumentException when the direction is not LEFT or RIGHT
         */
        default boolean isIndicatorOn(final LateralDirectionality lat)
        {
            Throw.when(lat == null || lat.equals(LateralDirectionality.NONE), IllegalArgumentException.class,
                    "Lateral direction should be LEFT or RIGHT.");
            return lat.isLeft() ? getTurnIndicatorStatus().isLeft() : getTurnIndicatorStatus().isRight();
        }

        /**
         * Returns the indicator status.
         * @return the indicator status
         */
        TurnIndicatorStatus getTurnIndicatorStatus();

        /**
         * Returns whether the braking lights are on.
         * @return whether the braking lights are on
         */
        boolean isBrakingLightsOn();

        /**
         * Wraps a GTU and returns its signals.
         * @param gtu GTU
         * @return signals view of the GTU
         */
        static Signals of(final LaneBasedGtu gtu)
        {
            return of(gtu, gtu.getSimulator().getSimulatorTime());
        }

        /**
         * Wraps a GTU and returns its signals.
         * @param gtu GTU
         * @param time simulation time of the signals
         * @return signals view of the GTU
         */
        static Signals of(final LaneBasedGtu gtu, final Duration time)
        {
            return new Signals()
            {
                @Override
                public TurnIndicatorStatus getTurnIndicatorStatus()
                {
                    return gtu.getTurnIndicatorStatus(time);
                }

                @Override
                public boolean isBrakingLightsOn()
                {
                    return gtu.isBrakingLightsOn(time);
                }
            };
        }

        /**
         * Record storing signals information.
         * @param getTurnIndicatorStatus the indicator status
         * @param isBrakingLightsOn whether the braking lights are on
         */
        record Record(TurnIndicatorStatus getTurnIndicatorStatus, boolean isBrakingLightsOn) implements Signals
        {

            /**
             * Constructor.
             * @param getTurnIndicatorStatus the indicator status
             * @param isBrakingLightsOn whether the braking lights are on
             * @throws NullPointerException when getTurnIndicatorStatus is {@code null}
             */
            public Record
            {
                Throw.whenNull(getTurnIndicatorStatus, "getTurnIndicatorStatus");
            }

        };
    }

    /**
     * Information on the maneuver.
     */
    interface Maneuver
    {
        /** Instance with no signals. */
        Maneuver NONE = new Record(false, false, Length.ZERO);

        /**
         * Returns whether the GTU is changing either left or right.
         * @param lat lateral lane change direction
         * @return whether the GTU is changing either left or right
         * @throws IllegalArgumentException when the direction is not LEFT or RIGHT
         */
        default boolean isChangingLane(final LateralDirectionality lat)
        {
            Throw.when(lat == null || lat.equals(LateralDirectionality.NONE), IllegalArgumentException.class,
                    "Lateral direction should be LEFT or RIGHT.");
            return lat.isLeft() ? isChangingLeft() : isChangingRight();
        }

        /**
         * Returns whether the GTU is changing lanes to the left.
         * @return whether the GTU is changing lanes to the left
         */
        boolean isChangingLeft();

        /**
         * Returns whether the GTU is changing lanes to the right.
         * @return whether the GTU is changing lanes to the right
         */
        boolean isChangingRight();

        /**
         * Returns the lateral deviation from the lane center line. Positive values are left, negative values are right.
         * @return lateral deviation from the lane center line
         */
        Length getDeviation();

        /**
         * Wraps a GTU and returns its signals.
         * @param gtu GTU
         * @return signals view of the GTU
         */
        static Maneuver of(final LaneBasedGtu gtu)
        {
            return of(gtu, gtu.getSimulator().getSimulatorTime());
        }

        /**
         * Wraps a GTU and returns its maneuver at given time.
         * @param gtu GTU
         * @param time time of the maneuver
         * @return maneuver view of the GTU
         */
        static Maneuver of(final LaneBasedGtu gtu, final Duration time)
        {
            Throw.whenNull(gtu, "gtu");
            Throw.whenNull(time, "time");
            return new Maneuver()
            {
                @Override
                public boolean isChangingLeft()
                {
                    return gtu.getLaneChangeDirection(time).isLeft();
                }

                @Override
                public boolean isChangingRight()
                {
                    return gtu.getLaneChangeDirection(time).isRight();
                }

                @Override
                public Length getDeviation()
                {
                    return gtu.getDeviation(time);
                }
            };
        }

        /**
         * Record storing signals information.
         * @param isChangingLeft whether the GTU is changing lanes to the left
         * @param isChangingRight whether the GTU is changing lanes to the right
         * @param getDeviation lateral deviation from the lane center line
         */
        record Record(boolean isChangingLeft, boolean isChangingRight, Length getDeviation) implements Maneuver
        {

            /**
             * Constructor.
             * @param isChangingLeft whether the GTU is changing lanes to the left
             * @param isChangingRight whether the GTU is changing lanes to the right
             * @param getDeviation lateral deviation from the lane center line
             * @throws IllegalArgumentException when both {@code isChangingLeft} and {@code isChangingRight} are true
             * @throws NullPointerException when {@code getDeviation} is {@code null}
             */
            public Record
            {
                Throw.when(isChangingLeft && isChangingRight, IllegalArgumentException.class,
                        "Both isChangingLeft and isChangingRight are true.");
                Throw.whenNull(getDeviation, "getDeviation");
            }
        };
    }

    /**
     * Information on the behavior.
     */
    interface Behavior
    {
        /**
         * Many models that observe a GTU need to predict the imminent behavior of that GTU. Having a car following model of the
         * observed GTU can help with that. The car following model that is returned can be on a continuum between the actual
         * car following model of the observed GTU and the own car following model of the observing GTU, not making any
         * assumptions about the observed GTU. When successive observations of the GTU take place, parameters about its behavior
         * can be estimated more accurately. Another interesting easy-to-implement solution is to return a car following model
         * per GTU type, where the following model of a truck can differ from that of a car.
         * @return a car following model that represents the expected behavior of the observed GTU
         */
        CarFollowingModel getCarFollowingModel();

        /**
         * Many models that observe a GTU need to predict the imminent behavior of that GTU. Having an estimate of the
         * behavioral characteristics of the observed GTU can help with that. The parameters that are returned can be on a
         * continuum between the actual parameters of the observed GTU and the own parameters of the observing GTU, not making
         * any assumptions about the observed GTU. When successive observations of the GTU take place, parameters about its
         * behavior can be estimated more accurately. Another interesting easy-to-implement solution is to return a set of
         * parameters per GTU type, where the parameters of a truck can differ from that of a car.
         * @return the parameters that represent the expected behavior of the observed GTU, in case of exact values a safe copy
         *         is returned
         */
        Parameters getParameters();

        /**
         * Many models that observe a GTU need to predict the imminent behavior of that GTU. Having a model of the speed info
         * profile for the observed GTU can help with predicting its future behavior. The speed limit info that is returned can
         * be on a continuum between the actual speed limit model of the observed GTU and the own speed limit model of the
         * observing GTU, not making any assumptions about the observed GTU. When successive observations of the GTU take place,
         * parameters about its behavior, such as the maximum speed it accepts, can be estimated more accurately. Another
         * interesting easy-to-implement solution is to return a speed limit info object per GTU type, where the returned
         * information of a truck -- with a maximum allowed speed on 80 km/h -- can differ from that of a car -- which can have
         * a maximum allowed speed of 100 km/h on the same road.
         * @return a speed limit model that helps in determining the expected behavior of the observed GTU
         */
        SpeedLimitInfo getSpeedLimitInfo();

        /**
         * Returns the perceived desired speed of the neighbor.
         * @return perceived desired speed of the neighbor
         */
        Speed getDesiredSpeed();

        /**
         * Models responding to other GTU may assume a route of the vehicle, for instance at intersections. The route may be
         * short, i.e. only over the next intersection. Implementations may return anything from the actual route, a route based
         * on indicators and other assumptions, or empty if simply not known/estimated.
         * @return route of GTU, empty if there is no route
         */
        Optional<Route> getRoute();

        /**
         * Returns the perceived left lane change desire, a value between -1 and 1.
         * @return the perceived left lane change desire, a value between -1 and 1
         */
        double leftLaneChangeDesire();

        /**
         * Returns the perceived right lane change desire, a value between -1 and 1.
         * @return the perceived right lane change desire, a value between -1 and 1
         */
        double rightLaneChangeDesire();

        /**
         * Returns the perceived social pressure, a value between 0 and 1.
         * @return the perceived social pressure, a value between 0 and 1
         */
        double socialPressure();

        /**
         * Wraps a GTU and returns its behavior. The given time only applies to the parameters, lane change desire and social
         * pressure.
         * @param gtu GTU
         * @return behavior view of the GTU
         * @throws NullPointerException when GTU is {@code null}
         */
        static Behavior of(final LaneBasedGtu gtu)
        {
            Throw.whenNull(gtu, "gtu");
            return of0(gtu, gtu.getSimulator().getSimulatorTime(), null);
        }

        /**
         * Wraps a GTU and returns its behavior. The given time only applies to lane change desire and social pressure.
         * @param gtu GTU
         * @param gtuTypeAssumptions assumptions on the GTU type
         * @return behavior view of the GTU
         * @throws NullPointerException when any input argument is {@code null}
         */
        static Behavior of(final LaneBasedGtu gtu, final GtuTypeAssumptions gtuTypeAssumptions)
        {
            Throw.whenNull(gtu, "gtu");
            Throw.whenNull(gtuTypeAssumptions, "gtuTypeAssumptions");
            return of0(gtu, gtu.getSimulator().getSimulatorTime(), gtuTypeAssumptions);
        }

        /**
         * Wraps a GTU and returns its behavior. The given time only applies to the parameters, lane change desire and social
         * pressure.
         * @param gtu GTU
         * @param time simulation time of the behavior
         * @return behavior view of the GTU
         * @throws NullPointerException when any input argument is {@code null}
         */
        static Behavior of(final LaneBasedGtu gtu, final Duration time)
        {
            return of0(gtu, time, null);
        }

        /**
         * Wraps a GTU and returns its behavior. The given time only applies to the lane change desire and social pressure.
         * @param gtu GTU
         * @param time simulation time of the behavior
         * @param gtuTypeAssumptions assumptions on the GTU type
         * @return behavior view of the GTU
         * @throws NullPointerException when any input argument is {@code null}
         */
        static Behavior of(final LaneBasedGtu gtu, final Duration time, final GtuTypeAssumptions gtuTypeAssumptions)
        {
            Throw.whenNull(gtuTypeAssumptions, "gtuTypeAssumptions");
            return of0(gtu, time, gtuTypeAssumptions);
        }

        /**
         * Wraps a GTU and returns its behavior. The given time only applies to the parameters, lane change desire and social
         * pressure.
         * @param gtu GTU
         * @param time simulation time of the behavior
         * @param gtuTypeAssumptions assumptions on the GTU type, can be {@code null}
         * @return behavior view of the GTU
         */
        private static Behavior of0(final LaneBasedGtu gtu, final Duration time, final GtuTypeAssumptions gtuTypeAssumptions)
        {
            Throw.whenNull(gtu, "gtu");
            Throw.whenNull(time, "time");
            // parameters are not historical, they could be, but that's really slow
            Parameters parameters = new ParameterSet(gtu.getParameters());
            return new Behavior()
            {
                /** Speed limit info. */
                private SpeedLimitInfo speedLimitInfo;

                @Override
                public CarFollowingModel getCarFollowingModel()
                {
                    return gtuTypeAssumptions == null ? gtu.getTacticalPlanner().getCarFollowingModel()
                            : gtuTypeAssumptions.getCarFollowingModel(gtu.getType());
                }

                @Override
                public Parameters getParameters()
                {
                    return gtuTypeAssumptions == null ? parameters : gtuTypeAssumptions.getParameters(gtu.getType());
                }

                @Override
                public SpeedLimitInfo getSpeedLimitInfo()
                {
                    if (this.speedLimitInfo == null)
                    {
                        this.speedLimitInfo = new SpeedLimitInfo();
                        this.speedLimitInfo.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED, gtu.getMaximumSpeed());
                        this.speedLimitInfo.addSpeedInfo(SpeedLimitTypes.FIXED_SIGN,
                                gtuTypeAssumptions == null
                                        ? Try.assign(() -> gtu.getLane().getSpeedLimit(gtu.getType()),
                                                "Unable to obtain speed limit for GTU on lane where it is at.")
                                        : gtuTypeAssumptions.getLaneTypeMaxSpeed(gtu.getType(), gtu.getLane().getType()));
                    }
                    return this.speedLimitInfo;
                }

                @Override
                public Speed getDesiredSpeed()
                {
                    return gtu.getDesiredSpeed();
                }

                @Override
                public Optional<Route> getRoute()
                {
                    return gtu.getStrategicalPlanner().getRoute();
                }

                @Override
                public double leftLaneChangeDesire()
                {
                    return parameters.getOptionalParameter(LmrsParameters.DLEFT).orElse(0.0);
                }

                @Override
                public double rightLaneChangeDesire()
                {
                    return parameters.getOptionalParameter(LmrsParameters.DRIGHT).orElse(0.0);
                }

                @Override
                public double socialPressure()
                {
                    return parameters.getOptionalParameter(LmrsParameters.SOCIO).orElse(0.0);
                }
            };
        }
    }

    /**
     * Returns a view of this perceived headway GTU that returns different values for the headway, speed and acceleration.
     * @param headway headway
     * @param speed speed
     * @param acceleration acceleration
     * @return copy with different headway, speed and acceleration
     * @throws NullPointerException when any input argument is {@code null}
     * @throws IllegalStateException when this perceived GTU is parallel
     */
    default PerceivedGtu moved(final Length headway, final Speed speed, final Acceleration acceleration)
    {
        Throw.whenNull(headway, "headyway");
        Throw.whenNull(speed, "speed");
        Throw.whenNull(acceleration, "acceleration");
        Throw.when(getKinematics().getOverlap().isParallel(), IllegalStateException.class,
                "GTU {} is moved in perception, but it is parallel.", getId());
        return new PerceivedGtu()
        {
            @Override
            public ObjectType getObjectType()
            {
                return PerceivedGtu.this.getObjectType();
            }

            @Override
            public Length getLength()
            {
                return PerceivedGtu.this.getLength();
            }

            @Override
            public Kinematics getKinematics()
            {
                return new Kinematics()
                {
                    @Override
                    public Speed getSpeed()
                    {
                        return speed;
                    }

                    @Override
                    public Length getDistance()
                    {
                        return headway;
                    }

                    @Override
                    public Acceleration getAcceleration()
                    {
                        return acceleration;
                    }

                    @Override
                    public boolean isFacingSameDirection()
                    {
                        return PerceivedGtu.this.getKinematics().isFacingSameDirection();
                    }

                    @Override
                    public Overlap getOverlap()
                    {
                        return PerceivedGtu.this.getKinematics().getOverlap();
                    }
                };
            }

            @Override
            public String getId()
            {
                return PerceivedGtu.this.getId();
            }

            @Override
            public GtuType getGtuType()
            {
                return PerceivedGtu.this.getGtuType();
            }

            @Override
            public Length getWidth()
            {
                return PerceivedGtu.this.getWidth();
            }

            @Override
            public Signals getSignals()
            {
                return PerceivedGtu.this.getSignals();
            }

            @Override
            public Maneuver getManeuver()
            {
                return PerceivedGtu.this.getManeuver();
            }

            @Override
            public Behavior getBehavior()
            {
                return PerceivedGtu.this.getBehavior();
            }
        };
    }

    /**
     * Returns perceived GTU with given kinematics.
     * @param gtu GTU that is perceived
     * @param kinematics kinematics for the vehicle
     * @return perceived view of the GTU
     * @throws NullPointerException when {@code gtu} is null
     */
    static PerceivedGtu of(final LaneBasedGtu gtu, final Kinematics kinematics)
    {
        Throw.whenNull(gtu, "gtu");
        return new PerceivedGtuBase(gtu.getId(), gtu.getType(), gtu.getLength(), gtu.getWidth(), kinematics, Signals.of(gtu),
                Maneuver.of(gtu), Behavior.of(gtu));
    }

    /**
     * Returns perceived GTU at the given time with given kinematics.
     * @param gtu GTU that is perceived
     * @param kinematics kinematics for the vehicle
     * @param time simulation time at which the GTU is perceived
     * @return perceived view of the GTU
     * @throws NullPointerException when {@code gtu} is null
     */
    static PerceivedGtu of(final LaneBasedGtu gtu, final Kinematics kinematics, final Duration time)
    {
        Throw.whenNull(gtu, "gtu");
        return new PerceivedGtuBase(gtu.getId(), gtu.getType(), gtu.getLength(), gtu.getWidth(), kinematics,
                Signals.of(gtu, time), Maneuver.of(gtu, time), Behavior.of(gtu, time));
    }

}