ChannelTaskSignal.java

package org.opentrafficsim.road.gtu.perception.mental.channel;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;

import org.djunits.value.vdouble.scalar.Length;
import org.opentrafficsim.base.DistancedObject;
import org.opentrafficsim.base.OtsRuntimeException;
import org.opentrafficsim.base.parameters.ParameterTypeDouble;
import org.opentrafficsim.base.parameters.ParameterTypeLength;
import org.opentrafficsim.base.parameters.constraint.NumericConstraint;
import org.opentrafficsim.road.gtu.LaneBasedGtu;
import org.opentrafficsim.road.gtu.perception.LanePerception;
import org.opentrafficsim.road.gtu.perception.RelativeLane;
import org.opentrafficsim.road.gtu.perception.categories.neighbors.NeighborsPerception;
import org.opentrafficsim.road.gtu.perception.mental.AbstractTask;

/**
 * Task demand for signal (front: brake lights, left: right indicator, right: left indicator). This applies to the first leader
 * with signal, and is defined as {@code TD_signal * (1 - s/x0)}, where {@code TD_signal} is a constant, {@code s} is the
 * distance to the leader with signal and {@code x0} is the look-ahead distance.
 * <p>
 * Copyright (c) 2024-2026 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 Wouter Schakel
 */
public class ChannelTaskSignal extends AbstractTask implements ChannelTask
{

    /** Signal task demand. */
    public static final ParameterTypeDouble TDSIGNAL =
            new ParameterTypeDouble("td_signal", "Signal task demand", 0.2, NumericConstraint.POSITIVEZERO);

    /** Distance discount range. */
    public static final ParameterTypeLength X0_D = ChannelMental.X0_D;

    /** Standard set of left, right and front signal task. */
    private static final Set<ChannelTask> SET =
            Set.of(new ChannelTaskSignal(LEFT), new ChannelTaskSignal(RIGHT), new ChannelTaskSignal(FRONT));

    /** Standard supplier that supplies instances for left, right and front signal task. */
    public static final Function<LanePerception, Set<ChannelTask>> SUPPLIER = (p) -> SET;

    /** Channel. */
    private final Object channel;

    /** Predicate to test the signal on a GTU. */
    private final Predicate<LaneBasedGtu> predicate;

    /** Relative lane that applies. */
    private final RelativeLane lane;

    /**
     * Constructor.
     * @param channel channel.
     */
    public ChannelTaskSignal(final Object channel)
    {
        super("signal");
        this.channel = channel;
        if (channel.equals(FRONT))
        {
            this.predicate = (t) -> t.isBrakingLightsOn();
            this.lane = RelativeLane.CURRENT;
        }
        else if (channel.equals(LEFT))
        {
            this.predicate = (t) -> t.getTurnIndicatorStatus().isRightOrBoth();
            this.lane = RelativeLane.LEFT;
        }
        else if (channel.equals(RIGHT))
        {
            this.predicate = (t) -> t.getTurnIndicatorStatus().isLeftOrBoth();
            this.lane = RelativeLane.RIGHT;
        }
        else
        {
            throw new IllegalArgumentException("Channel " + channel + " is not supported by signal channel.");
        }
    }

    @Override
    public String getId()
    {
        return String.format("signal (%s)", this.channel);
    }

    @Override
    public Object getChannel()
    {
        return this.channel;
    }

    @Override
    public double calculateTaskDemand(final LanePerception perception)
    {
        NeighborsPerception neighbors = perception.getPerceptionCategoryOptional(NeighborsPerception.class)
                .orElseThrow(() -> new NoSuchElementException("NeighborsPerception not present."));
        Iterator<DistancedObject<LaneBasedGtu>> leaders = neighbors.getLeaders(this.lane).underlyingWithDistance();
        while (leaders.hasNext())
        {
            DistancedObject<LaneBasedGtu> leader = leaders.next();
            if (this.predicate.test(leader.object()))
            {
                Length x0 = perception.getGtu().getParameters().getOptionalParameter(X0_D)
                        .orElseThrow(() -> new OtsRuntimeException("Parameter x0_d not present."));
                double tdSignal = perception.getGtu().getParameters().getOptionalParameter(TDSIGNAL)
                        .orElseThrow(() -> new OtsRuntimeException("Parameter td_signal not available."));
                return tdSignal * (1.0 - leader.distance().si / x0.si);
            }
        }
        return 0.0;
    }

}