SimpleOperationalPlan.java

package org.opentrafficsim.road.gtu.lane.plan.operational;

import java.io.Serializable;

import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.scalar.Length;
import org.djutils.exceptions.Throw;
import org.djutils.logger.CategoryLogger;
import org.opentrafficsim.core.gtu.GtuException;
import org.opentrafficsim.core.gtu.TurnIndicatorIntent;
import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
import org.opentrafficsim.core.network.LateralDirectionality;
import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;

/**
 * Simplified plan containing an acceleration value and possible lane change direction.
 * <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://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
 */

public class SimpleOperationalPlan implements Serializable
{

    /** */
    private static final long serialVersionUID = 20160811L;

    /** Acceleration. */
    private Acceleration acceleration;

    /** Lane change direction. */
    private final LateralDirectionality laneChangeDirection;

    /** Indicator intent. */
    private TurnIndicatorIntent indicatorIntent = TurnIndicatorIntent.NONE;

    /** Distance to object causing turn indicator intent. */
    private Length indicatorObjectDistance = null;

    /** Duration of the plan. */
    private final Duration duration;

    /**
     * @param acceleration Acceleration; acceleration
     * @param duration Duration; duration
     */
    public SimpleOperationalPlan(final Acceleration acceleration, final Duration duration)
    {
        this(acceleration, duration, LateralDirectionality.NONE);
    }

    /**
     * @param acceleration Acceleration; acceleration
     * @param duration Duration; duration
     * @param laneChangeDirection LateralDirectionality; lane change direction, may be {@code null}.
     */
    public SimpleOperationalPlan(final Acceleration acceleration, final Duration duration,
            final LateralDirectionality laneChangeDirection)
    {
        Throw.whenNull(acceleration, "Acceleration may not be null.");
        Throw.whenNull(duration, "Duration may not be null.");
        Throw.whenNull(laneChangeDirection, "Lane change direction may not be null.");
        checkAcceleration(acceleration);
        this.acceleration = Acceleration.max(Acceleration.instantiateSI(-100.0), acceleration);
        this.duration = duration;
        this.laneChangeDirection = laneChangeDirection;
    }

    /**
     * @return acceleration.
     */
    public final Acceleration getAcceleration()
    {
        return this.acceleration;
    }

    /**
     * Sets acceleration.
     * @param acceleration Acceleration; acceleration
     */
    public final void setAcceleration(final Acceleration acceleration)
    {
        checkAcceleration(acceleration);
        this.acceleration = acceleration;
    }

    /**
     * @return duration.
     */
    public Duration getDuration()
    {
        return this.duration;
    }

    /**
     * @return if lane change.
     */
    public final boolean isLaneChange()
    {
        return this.laneChangeDirection != LateralDirectionality.NONE;
    }

    /**
     * @return laneChangeDirection, may be NONE if no lane change.
     */
    public final LateralDirectionality getLaneChangeDirection()
    {
        return this.laneChangeDirection;
    }

    /**
     * Set minimum of current and given acceleration.
     * @param a Acceleration; acceleration to set if lower than current acceleration
     */
    public final void minimizeAcceleration(final Acceleration a)
    {
        checkAcceleration(a);
        // XXX: AV
        this.acceleration = Acceleration.max(Acceleration.instantiateSI(-100.0), Acceleration.min(this.acceleration, a));
    }

    /**
     * Check acceleration level.
     * @param a Acceleration; acceleration
     */
    private void checkAcceleration(final Acceleration a)
    {
        if (a.equals(Acceleration.NEGATIVE_INFINITY) || a.equals(Acceleration.NEG_MAXVALUE))
        {
            // XXX: AV
            CategoryLogger.always().error("Model has calculated a negative infinite or negative max value acceleration.");
        }
    }

    /**
     * @return indicatorIntent.
     */
    public final TurnIndicatorIntent getIndicatorIntent()
    {
        return this.indicatorIntent;
    }

    /**
     * Set left indicator intent. Any intent given with distance overrules this intent.
     */
    public final void setIndicatorIntentLeft()
    {
        if (this.indicatorObjectDistance != null)
        {
            return;
        }
        if (this.indicatorIntent.isRight())
        {
            this.indicatorIntent = TurnIndicatorIntent.CONFLICTING;
        }
        else
        {
            this.indicatorIntent = TurnIndicatorIntent.LEFT;
        }
    }

    /**
     * Set right indicator intent. Any intent given with distance overrules this intent.
     */
    public final void setIndicatorIntentRight()
    {
        if (this.indicatorObjectDistance != null)
        {
            return;
        }
        if (this.indicatorIntent.isLeft())
        {
            this.indicatorIntent = TurnIndicatorIntent.CONFLICTING;
        }
        else
        {
            this.indicatorIntent = TurnIndicatorIntent.RIGHT;
        }
    }

    /**
     * Set left indicator intent. Intent with smallest provided distance has priority.
     * @param distance Length; distance to object pertaining to the turn indicator intent
     */
    public final void setIndicatorIntentLeft(final Length distance)
    {
        if (compareAndIgnore(distance))
        {
            return;
        }
        if (this.indicatorIntent.isRight())
        {
            this.indicatorIntent = TurnIndicatorIntent.CONFLICTING;
        }
        else
        {
            this.indicatorIntent = TurnIndicatorIntent.LEFT;
        }

    }

    /**
     * Set right indicator intent. Intent with smallest provided distance has priority.
     * @param distance Length; distance to object pertaining to the turn indicator intent
     */
    public final void setIndicatorIntentRight(final Length distance)
    {
        if (compareAndIgnore(distance))
        {
            return;
        }
        if (this.indicatorIntent.isLeft())
        {
            this.indicatorIntent = TurnIndicatorIntent.CONFLICTING;
        }
        else
        {
            this.indicatorIntent = TurnIndicatorIntent.RIGHT;
        }
    }

    /**
     * Compares distances and returns whether the given distance (and intent) can be ignored.
     * @param distance Length; distance to object of intent
     * @return whether the given distance can be ignored
     */
    private boolean compareAndIgnore(final Length distance)
    {
        if (this.indicatorObjectDistance != null)
        {
            if (this.indicatorObjectDistance.lt(distance))
            {
                // disregard input; the intent from larger distance
                return true;
            }
            if (this.indicatorObjectDistance.gt(distance))
            {
                // disregard existing; the intent from larger distance
                this.indicatorIntent = TurnIndicatorIntent.NONE; // prevents a set to CONFLICTING
            }
        }
        else
        {
            // disregard existing; the intent without distance
            this.indicatorIntent = TurnIndicatorIntent.NONE; // prevents a set to CONFLICTING
        }
        return false;
    }

    /** {@inheritDoc} */
    @Override
    @SuppressWarnings("checkstyle:designforextension")
    public String toString()
    {
        return "SimpleOperationalPlan [Acceleration=" + this.acceleration + ", change=" + this.laneChangeDirection
                + ", indicator intent=" + this.indicatorIntent + "]";
    }

    /**
     * @param gtu LaneBasedGtu; LaneBasedGtu to set the indicator on
     * @throws GtuException if GTU does not support the indicator
     */
    public final void setTurnIndicator(final LaneBasedGtu gtu) throws GtuException
    {
        if (this.indicatorIntent.isLeft())
        {
            gtu.setTurnIndicatorStatus(TurnIndicatorStatus.LEFT);
        }
        else if (this.indicatorIntent.isRight())
        {
            gtu.setTurnIndicatorStatus(TurnIndicatorStatus.RIGHT);
        }
        else
        {
            gtu.setTurnIndicatorStatus(TurnIndicatorStatus.NONE);
        }
    }

}