RelativeLane.java

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

import java.io.Serializable;

import org.djutils.exceptions.Throw;
import org.opentrafficsim.core.network.LateralDirectionality;

/**
 * Defines a lane relative to the current lane.
 * <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/wjschakel">Wouter Schakel</a>
 */
public class RelativeLane implements Comparable<RelativeLane>, Serializable
{

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

    /** Second left lane. */
    public static final RelativeLane SECOND_LEFT = new RelativeLane(LateralDirectionality.LEFT, 2);

    /** Left lane. */
    public static final RelativeLane LEFT = new RelativeLane(LateralDirectionality.LEFT, 1);

    /** Current lane. */
    public static final RelativeLane CURRENT = new RelativeLane(LateralDirectionality.NONE, 0);

    /** right lane. */
    public static final RelativeLane RIGHT = new RelativeLane(LateralDirectionality.RIGHT, 1);

    /** Second right lane. */
    public static final RelativeLane SECOND_RIGHT = new RelativeLane(LateralDirectionality.RIGHT, 2);

    /**
     * Rank, summarizes both the lateral directionality and the number of lanes. Is zero for CURRENT, otherwise equal to number
     * of lanes for RIGHT, negative number of lanes for LEFT.
     */
    private final int rank;

    /**
     * Private constructor.
     * @param rank int; the rank
     */
    private RelativeLane(final int rank)
    {
        this.rank = rank;
    }

    /**
     * Constructor.
     * @param lat LateralDirectionality; lateral direction (use {@code null} for the current lane)
     * @param numLanes int; number of lanes in the lateral direction (not important for the current lane)
     * @throws IllegalArgumentException if numLanes is not at least 1, except if {@code lat == null} (current lane)
     * @throws IllegalArgumentException if numLanes is not 0 if {@code lat == null} (current lane)
     */
    public RelativeLane(final LateralDirectionality lat, final int numLanes)
    {
        Throw.whenNull(lat, "Lateral directionality may not be null.");
        Throw.when(lat.isNone() && numLanes != 0, IllegalArgumentException.class,
                "Number of lanes must be zero if the lateral directionality is NONE.");
        Throw.when((!lat.isNone()) && numLanes <= 0, IllegalArgumentException.class,
                "Relative lane with %d lanes in %s direction is not allowed, use values > 0.", numLanes, lat);
        this.rank = lat.isLeft() ? -numLanes : numLanes;
    }

    /**
     * Returns the lateral direction.
     * @return LateralDirectionality; the lateral direction
     */
    public final LateralDirectionality getLateralDirectionality()
    {
        // return this.lat;
        return this.rank == 0 ? LateralDirectionality.NONE
                : this.rank < 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
    }

    /**
     * Returns the number of lanes in the lateral direction.
     * @return int; number of lanes in the lateral direction
     */
    public final int getNumLanes()
    {
        return Math.abs(this.rank);
    }

    /**
     * Returns whether the second left lane is referred to.
     * @return whether the second left lane is referred to
     */
    public final boolean isSecondLeft()
    {
        return this.equals(SECOND_LEFT);
    }

    /**
     * Returns whether the left lane is referred to.
     * @return whether the left lane is referred to
     */
    public final boolean isLeft()
    {
        return this.equals(LEFT);
    }

    /**
     * Returns whether the current lane is referred to.
     * @return whether the current lane is referred to
     */
    public final boolean isCurrent()
    {
        return this.equals(CURRENT);
    }

    /**
     * Returns whether the right lane is referred to.
     * @return whether the right lane is referred to
     */
    public final boolean isRight()
    {
        return this.equals(RIGHT);
    }

    /**
     * Returns whether the second right lane is referred to.
     * @return whether the second right lane is referred to
     */
    public final boolean isSecondRight()
    {
        return this.equals(SECOND_RIGHT);
    }

    /**
     * Returns the left hand relative lane of this relative lane.
     * @return left hand relative lane of this relative lane.
     */
    public final RelativeLane getLeft()
    {
        return this.add(LEFT);
    }

    /**
     * Returns the right hand relative lane of this relative lane.
     * @return right hand relative lane of this relative lane.
     */
    public final RelativeLane getRight()
    {
        return this.add(RIGHT);
    }

    /**
     * Returns the relative lane relative to this lane, for example "the left lane" of "the 3rd right lane" is "the 2nd right
     * lane".
     * @param relativeLane RelativeLane; relative lane to get of this lane
     * @return relative lane relative to this lane
     */
    public final RelativeLane add(final RelativeLane relativeLane)
    {
        return new RelativeLane(this.rank + relativeLane.rank);
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + this.rank;
        return result;
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(final Object obj)
    {
        if (this == obj)
        {
            return true;
        }
        if (obj == null)
        {
            return false;
        }
        if (getClass() != obj.getClass())
        {
            return false;
        }
        RelativeLane other = (RelativeLane) obj;
        if (this.rank != other.rank) // relative lane is uniquely defined by the rank
        {
            return false;
        }
        return true;
    }

    /** {@inheritDoc} */
    @Override
    public final String toString()
    {
        if (this.equals(CURRENT))
        {
            return "RelativeLane [CURRENT]";
        }
        return new StringBuilder("RelativeLane [").append(getLateralDirectionality()).append(", ").append(getNumLanes())
                .append("]").toString();
    }

    /** {@inheritDoc} */
    @Override
    public final int compareTo(final RelativeLane rel)
    {
        return this.rank - rel.rank;
    }

}