LaneStructureRecord.java

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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.djunits.value.vdouble.scalar.Length;
import org.opentrafficsim.core.gtu.GTUDirectionality;
import org.opentrafficsim.core.gtu.GTUException;
import org.opentrafficsim.core.network.Node;
import org.opentrafficsim.road.network.lane.Lane;

import nl.tudelft.simulation.language.Throw;

/**
 * A LaneStructureRecord contains information about the lanes that can be accessed from this lane by a GTUType. It tells whether
 * there is a left and/or right lane by pointing to other LaneStructureRecords, and which successor LaneStructureRecord(s) there
 * are at the end of the lane of this LaneStructureRecord. All information (left, right, next) is calculated relative to the
 * driving direction of the GTU that owns this structure.
 * <p>
 * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
 * </p>
 * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
 * initial version Feb 21, 2016 <br>
 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
 */
public class LaneStructureRecord implements Serializable
{
    /** */
    private static final long serialVersionUID = 20160400L;

    /** The lane of the LSR. */
    private final Lane lane;

    /** The direction in which we process this lane. */
    private final GTUDirectionality gtuDirectionality;

    /** The left LSR or null if not available. Left and right are relative to the <b>driving</b> direction. */
    private LaneStructureRecord left;

    /** The right LSR or null if not available. Left and right are relative to the <b>driving</b> direction. */
    private LaneStructureRecord right;

    /** Where this lane was cut-off resulting in no next lanes, if so. */
    private Length cutOffEnd = null;

    /** Where this lane was cut-off resulting in no prev lanes, if so. */
    private Length cutOffStart = null;

    /** Distance to start of the record, negative for backwards. */
    private final Length startDistance;

    /**
     * The next LSRs. The list is empty if no LSRs are available. Next is relative to the driving direction, not to the design
     * line direction.
     */
    private List<LaneStructureRecord> nextList = new ArrayList<>();

    /**
     * The previous LSRs. The list is empty if no LSRs are available. Previous is relative to the driving direction, not to the
     * design line direction.
     */
    private List<LaneStructureRecord> prevList = new ArrayList<>();

    /**
     * @param lane the lane of the LSR
     * @param direction the direction on which we process this lane
     * @param startDistance distance to start of the record, negative for backwards
     */
    public LaneStructureRecord(final Lane lane, final GTUDirectionality direction, final Length startDistance)
    {
        this.lane = lane;
        this.gtuDirectionality = direction;
        this.startDistance = startDistance;
    }

    /**
     * @return the 'from' node of the link belonging to this lane, in the driving direction.
     */
    public final Node getFromNode()
    {
        return this.gtuDirectionality.isPlus() ? this.lane.getParentLink().getStartNode()
                : this.lane.getParentLink().getEndNode();
    }

    /**
     * @return the 'to' node of the link belonging to this lane, in the driving direction.
     */
    public final Node getToNode()
    {
        return this.gtuDirectionality.isPlus() ? this.lane.getParentLink().getEndNode()
                : this.lane.getParentLink().getStartNode();
    }

    /**
     * Returns total distance towards the object at the given position. This method accounts for the GTU directionality.
     * @param longitudinalPosition position on the design line
     * @return total distance towards the object at the given position
     */
    public final Length getDistanceToPosition(final Length longitudinalPosition)
    {
        return this.startDistance.plus(
                this.gtuDirectionality.isPlus() ? longitudinalPosition : this.lane.getLength().minus(longitudinalPosition));
    }

    /**
     * @return whether the link to which this lane belongs splits, i.e. some of the parallel, connected lanes lead to a
     *         different destination than others
     */
    public final boolean isLinkSplit()
    {
        Set<Node> toNodes = new HashSet<>();
        LaneStructureRecord lsr = this;
        while (lsr != null)
        {
            for (LaneStructureRecord next : lsr.getNext())
            {
                toNodes.add(next.getToNode());
            }
            lsr = lsr.getLeft();
        }
        lsr = this.getRight();
        while (lsr != null)
        {
            for (LaneStructureRecord next : lsr.getNext())
            {
                toNodes.add(next.getToNode());
            }
            lsr = lsr.getRight();
        }
        return toNodes.size() > 1;
    }

    /**
     * @return whether the link to which this lane belongs merges, i.e. some of the parallel, connected lanes follow from a
     *         different origin than others
     */
    public final boolean isLinkMerge()
    {
        Set<Node> fromNodes = new HashSet<>();
        LaneStructureRecord lsr = this;
        while (lsr != null)
        {
            for (LaneStructureRecord prev : lsr.getPrev())
            {
                fromNodes.add(prev.getFromNode());
            }
            lsr = lsr.getLeft();
        }
        lsr = this.getRight();
        while (lsr != null)
        {
            for (LaneStructureRecord prev : lsr.getPrev())
            {
                fromNodes.add(prev.getFromNode());
            }
            lsr = lsr.getRight();
        }
        return fromNodes.size() > 1;
    }

    /**
     * @return the left LSR or null if not available. Left and right are relative to the <b>driving</b> direction.
     */
    public final LaneStructureRecord getLeft()
    {
        return this.left;
    }

    /**
     * @param left set the left LSR or null if not available. Left and right are relative to the <b>driving</b> direction.
     */
    public final void setLeft(final LaneStructureRecord left)
    {
        this.left = left;
    }

    /**
     * @return the right LSR or null if not available. Left and right are relative to the <b>driving</b> direction
     */
    public final LaneStructureRecord getRight()
    {
        return this.right;
    }

    /**
     * @param right set the right LSR or null if not available. Left and right are relative to the <b>driving</b> direction
     */
    public final void setRight(final LaneStructureRecord right)
    {
        this.right = right;
    }

    /**
     * @return the next LSRs. The list is empty if no LSRs are available. Next is relative to the driving direction, not to the
     *         design line direction.
     */
    public final List<LaneStructureRecord> getNext()
    {
        return this.nextList;
    }

    /**
     * @param nextList set the next LSRs. The list is empty if no LSRs are available. Next is relative to the driving direction,
     *            not to the design line direction.
     * @throws GTUException if the records is cut-off at the end
     */
    public final void setNextList(final List<LaneStructureRecord> nextList) throws GTUException
    {
        Throw.when(this.cutOffEnd != null && !nextList.isEmpty(), GTUException.class,
                "Cannot set next records to a record that was cut-off at the end.");
        this.nextList = nextList;
    }

    /**
     * @param next a next LSRs to add. Next is relative to the driving direction, not to the design line direction.
     * @throws GTUException if the records is cut-off at the end
     */
    public final void addNext(final LaneStructureRecord next) throws GTUException
    {
        Throw.when(this.cutOffEnd != null, GTUException.class,
                "Cannot add next records to a record that was cut-off at the end.");
        this.nextList.add(next);
    }

    /**
     * @return the previous LSRs. The list is empty if no LSRs are available. Previous is relative to the driving direction, not
     *         to the design line direction.
     */
    public final List<LaneStructureRecord> getPrev()
    {
        return this.prevList;
    }

    /**
     * @param prevList set the next LSRs. The list is empty if no LSRs are available. Previous is relative to the driving
     *            direction, not to the design line direction.
     * @throws GTUException if the records is cut-off at the start
     */
    public final void setPrevList(final List<LaneStructureRecord> prevList) throws GTUException
    {
        Throw.when(this.cutOffStart != null && !prevList.isEmpty(), GTUException.class,
                "Cannot set previous records to a record that was cut-off at the start.");
        this.prevList = prevList;
    }

    /**
     * @param prev a previous LSRs to add. Previous is relative to the driving direction, not to the design line direction.
     * @throws GTUException if the records is cut-off at the start
     */
    public final void addPrev(final LaneStructureRecord prev) throws GTUException
    {
        Throw.when(this.cutOffStart != null, GTUException.class,
                "Cannot add previous records to a record that was cut-off at the start.");
        this.prevList.add(prev);
    }

    /**
     * Sets this record as being cut-off, i.e. there are no next records due to cut-off.
     * @param cutOffEnd where this lane was cut-off (in the driving direction) resulting in no prev lanes
     * @throws GTUException if there are next records
     */
    public final void setCutOffEnd(final Length cutOffEnd) throws GTUException
    {
        Throw.when(!this.nextList.isEmpty(), GTUException.class,
                "Setting lane record with cut-off end, but there are next records.");
        this.cutOffEnd = cutOffEnd;
    }

    /**
     * Sets this record as being cut-off, i.e. there are no previous records due to cut-off.
     * @param cutOffStart where this lane was cut-off (in the driving direction) resulting in no next lanes
     * @throws GTUException if there are previous records
     */
    public final void setCutOffStart(final Length cutOffStart) throws GTUException
    {
        Throw.when(!this.prevList.isEmpty(), GTUException.class,
                "Setting lane record with cut-off start, but there are previous records.");
        this.cutOffStart = cutOffStart;
    }

    /**
     * Returns whether this lane has no next records as the lane structure was cut-off.
     * @return whether this lane has no next records as the lane structure was cut-off
     */
    public final boolean isCutOffEnd()
    {
        return this.cutOffEnd != null;
    }

    /**
     * Returns whether this lane has no previous records as the lane structure was cut-off.
     * @return whether this lane has no previous records as the lane structure was cut-off
     */
    public final boolean isCutOffStart()
    {
        return this.cutOffStart != null;
    }
    
    /**
     * Returns distance where the structure was cut-off.
     * @return distance where the structure was cut-off
     */
    public final Length getCutOffEnd()
    {
        return this.cutOffEnd;
    }

    /**
     * Returns distance where the structure was cut-off.
     * @return distance where the structure was cut-off
     */
    public final Length getCutOffStart()
    {
        return this.cutOffStart;
    }

    /**
     * @return the lane of the LSR
     */
    public final Lane getLane()
    {
        return this.lane;
    }

    /**
     * @return the direction in which we process this lane
     */
    public final GTUDirectionality getDirection()
    {
        return this.gtuDirectionality;
    }

    /**
     * @return startDistance.
     */
    public final Length getStartDistance()
    {
        return this.startDistance;
    }

    /** {@inheritDoc} */
    @Override
    public final String toString()
    {
        return "LaneStructureRecord [lane=" + this.lane + ", direction=" + this.gtuDirectionality + ", left=" + this.left
                + ", right=" + this.right + ", nextList=" + this.nextList + "]";
    }

}