DirectionalPolyLine.java

package org.opentrafficsim.base.geometry;

import java.util.List;

import org.djunits.value.vdouble.scalar.Direction;
import org.djutils.draw.line.PolyLine2d;
import org.djutils.draw.line.Ray2d;
import org.djutils.draw.point.OrientedPoint2d;
import org.djutils.draw.point.Point2d;
import org.djutils.exceptions.Throw;

/**
 * Adds a direction at the start and end point relative to its super class {@code OtsLine2d}, as the first and last segment may
 * not have the same direction as a theoretical line the segments are a numerical approach of. These directions are used in a
 * few methods which alter the result from the super class. The most notable addition of this class is
 * {@code directionalOffsetLine}.
 */
public class DirectionalPolyLine extends OtsLine2d
{
    /** */
    private static final long serialVersionUID = 20241130L;

    /** Start direction. */
    private final Direction startDirection;

    /** End direction. */
    private final Direction endDirection;

    /**
     * Constructor.
     * @param line base line
     * @param startDirection start direction
     * @param endDirection end direction
     */
    public DirectionalPolyLine(final PolyLine2d line, final Direction startDirection, final Direction endDirection)
    {
        super(line);
        Throw.whenNull(startDirection, "startDirection");
        Throw.whenNull(endDirection, "endDirection");
        this.startDirection = startDirection;
        this.endDirection = endDirection;
    }

    /**
     * Returns line at a fixed offset, adhering to end-point directions.
     * @param offset offset
     * @return offset line
     */
    public DirectionalPolyLine directionalOffsetLine(final double offset)
    {
        PolyLine2d offsetLine = offsetLine(offset);
        OrientedPoint2d start = new OrientedPoint2d(getFirst().x, getFirst().y, this.startDirection.si);
        OrientedPoint2d end = new OrientedPoint2d(getLast().x, getLast().y, this.endDirection.si);
        List<Point2d> points = offsetLine.getPointList();
        points.set(0, OtsGeometryUtil.offsetPoint(start, offset));
        points.set(points.size() - 1, OtsGeometryUtil.offsetPoint(end, offset));
        return new DirectionalPolyLine(new PolyLine2d(points), this.startDirection, this.endDirection);
    }

    /**
     * Returns line at a fixed offset, adhering to end-point directions.
     * @param startOffset offset at start
     * @param endOffset offset at end
     * @return offset line
     */
    public DirectionalPolyLine directionalOffsetLine(final double startOffset, final double endOffset)
    {
        PolyLine2d start = directionalOffsetLine(startOffset);
        PolyLine2d end = directionalOffsetLine(endOffset);
        return new DirectionalPolyLine(start.transitionLine(end, (f) -> f), this.startDirection, this.endDirection);
    }

    @Override
    public DirectionalPolyLine extractFractional(final double start, final double end)
    {
        return new DirectionalPolyLine(super.extractFractional(start, end),
                Direction.instantiateSI(getLocationFraction(start).phi), Direction.instantiateSI(getLocationFraction(end).phi));
    }

    @Override
    public Ray2d getLocationFraction(final double fraction)
    {
        Ray2d ray = super.getLocationFraction(fraction);
        if (fraction == 0.0)
        {
            ray = new Ray2d(ray, this.startDirection.si);
        }
        else if (fraction == 1.0)
        {
            ray = new Ray2d(ray, this.endDirection.si);
        }
        return ray;
    }

    /**
     * Fractional projection applied with the internal start and end direction.
     * @param x x-coordinate
     * @param y y-coordinate
     * @param fallback fallback method
     * @return fraction along line which it the projection of the given coordinate
     */
    public double projectFractional(final double x, final double y, final FractionalFallback fallback)
    {
        return projectFractional(this.startDirection, this.endDirection, x, y, fallback);
    }

    /**
     * Returns the start direction.
     * @return start direction
     */
    public Direction getStartDirection()
    {
        return this.startDirection;
    }

    /**
     * Returns the end direction.
     * @return end direction
     */
    public Direction getEndDirection()
    {
        return this.endDirection;
    }
}