SliceInfo.java

package org.opentrafficsim.road.network.lane;

import java.util.List;

import org.djunits.value.vdouble.scalar.Length;
import org.opentrafficsim.core.network.LateralDirectionality;

/**
 * Slice info. Can be used {@code Lane} but also the editor.
 * <p>
 * Copyright (c) 2023-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 SliceInfo
{

    /** The offsets and widths at positions along the line, relative to the design line of the parent link. */
    private final List<CrossSectionSlice> crossSectionSlices;

    /** Length of element for which slices are defined. */
    private final Length length;

    /**
     * Constructor.
     * @param crossSectionSlices List&lt;CrossSectionSlice&gt;; slices.
     * @param length Length; length of element for which slices are defined.
     */
    public SliceInfo(final List<CrossSectionSlice> crossSectionSlices, final Length length)
    {
        this.crossSectionSlices = crossSectionSlices;
        this.length = length;
    }

    /**
     * Retrieve the lateral offset from the Link design line at the specified longitudinal position.
     * @param fractionalPosition double; fractional longitudinal position on this Lane
     * @return Length; the lateralCenterPosition at the specified longitudinal position
     */
    public final Length getLateralCenterPosition(final double fractionalPosition)
    {
        if (this.crossSectionSlices.size() == 1)
        {
            return this.getOffsetAtBegin();
        }
        if (this.crossSectionSlices.size() == 2)
        {
            return Length.interpolate(this.getOffsetAtBegin(), this.getOffsetAtEnd(), fractionalPosition);
        }
        int sliceNr = calculateSliceNumber(fractionalPosition);
        double segmentPosition = fractionalPositionSegment(fractionalPosition, sliceNr);
        return Length.interpolate(this.crossSectionSlices.get(sliceNr).getOffset(),
                this.crossSectionSlices.get(sliceNr + 1).getOffset(), segmentPosition);
    }

    /**
     * Return the width of this CrossSectionElement at a specified fractional longitudinal position.
     * @param fractionalPosition double; the fractional longitudinal position
     * @return Length; the width of this CrossSectionElement at the specified fractional longitudinal position.
     */
    public final Length getWidth(final double fractionalPosition)
    {
        if (this.crossSectionSlices.size() == 1)
        {
            return this.getBeginWidth();
        }
        if (this.crossSectionSlices.size() == 2)
        {
            return Length.interpolate(this.getBeginWidth(), this.getEndWidth(), fractionalPosition);
        }
        int sliceNr = calculateSliceNumber(fractionalPosition);
        double segmentPosition = fractionalPositionSegment(fractionalPosition, sliceNr);
        return Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
                this.crossSectionSlices.get(sliceNr + 1).getWidth(), segmentPosition);
    }

    /**
     * Calculate the slice the fractional position is in.
     * @param fractionalPosition double; the fractional position between 0 and 1 compared to the design line
     * @return int; the lower slice number between 0 and number of slices - 1.
     */
    private int calculateSliceNumber(final double fractionalPosition)
    {
        for (int i = 0; i < this.crossSectionSlices.size() - 1; i++)
        {
            if (fractionalPosition >= this.crossSectionSlices.get(i).getRelativeLength().si / this.length.si
                    && fractionalPosition <= this.crossSectionSlices.get(i + 1).getRelativeLength().si / this.length.si)
            {
                return i;
            }
        }
        return this.crossSectionSlices.size() - 2;
    }

    /**
     * Returns the fractional position along the segment between two cross-section slices.
     * @param fractionalPosition double; fractional position on the whole link.
     * @param sliceNumber int; slice number at the start of the segment.
     * @return double; fractional position along the segment between two cross-section slices.
     */
    private double fractionalPositionSegment(final double fractionalPosition, final int sliceNumber)
    {
        double startPos = this.crossSectionSlices.get(sliceNumber).getRelativeLength().si / this.length.si;
        double endPos = this.crossSectionSlices.get(sliceNumber + 1).getRelativeLength().si / this.length.si;
        return (fractionalPosition - startPos) / (endPos - startPos);
    }

    /**
     * Retrieve the offset from the design line at the begin of the parent link.
     * @return Length; the offset of this CrossSectionElement at the begin of the parent link
     */
    public final Length getOffsetAtBegin()
    {
        return this.crossSectionSlices.get(0).getOffset();
    }

    /**
     * Retrieve the offset from the design line at the end of the parent link.
     * @return Length; the offset of this CrossSectionElement at the end of the parent link
     */
    public final Length getOffsetAtEnd()
    {
        return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getOffset();
    }

    /**
     * Retrieve the width at the begin of the parent link.
     * @return Length; the width of this CrossSectionElement at the begin of the parent link
     */
    public final Length getBeginWidth()
    {
        return this.crossSectionSlices.get(0).getWidth();
    }

    /**
     * Retrieve the width at the end of the parent link.
     * @return Length; the width of this CrossSectionElement at the end of the parent link
     */
    public final Length getEndWidth()
    {
        return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getWidth();
    }

    /**
     * Return the lateral offset from the design line of the parent Link of the Left or Right boundary of this
     * CrossSectionElement at the specified fractional longitudinal position.
     * @param lateralDirection LateralDirectionality; LEFT, or RIGHT
     * @param fractionalLongitudinalPosition double; ranges from 0.0 (begin of parentLink) to 1.0 (end of parentLink)
     * @return Length
     */
    public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
            final double fractionalLongitudinalPosition)
    {
        Length offset;
        Length halfWidth;
        if (this.crossSectionSlices.size() <= 2)
        {
            offset = Length.interpolate(getOffsetAtBegin(), getOffsetAtEnd(), fractionalLongitudinalPosition);
            halfWidth = Length.interpolate(getBeginWidth(), getEndWidth(), fractionalLongitudinalPosition).times(0.5);
        }
        else
        {
            int sliceNr = calculateSliceNumber(fractionalLongitudinalPosition);
            double segmentPosition = fractionalPositionSegment(fractionalLongitudinalPosition, sliceNr);
            offset = Length.interpolate(this.crossSectionSlices.get(sliceNr).getOffset(),
                    this.crossSectionSlices.get(sliceNr + 1).getOffset(), segmentPosition);
            halfWidth = Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
                    this.crossSectionSlices.get(sliceNr + 1).getWidth(), segmentPosition).times(0.5);
        }

        switch (lateralDirection)
        {
            case LEFT:
                return offset.minus(halfWidth);
            case RIGHT:
                return offset.plus(halfWidth);
            default:
                throw new Error("Bad switch on LateralDirectionality " + lateralDirection);
        }
    }

}