SubstringLine.java

/**
 *
 */
package org.opentrafficsim.road.network.factory.vissim;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateList;
import com.vividsolutions.jts.geom.LineSegment;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.util.Assert;

/**
 * @author P070518
 */
public class SubstringLine
{
    /**
     * Computes a substring of a {@link LineString} between given distances along the line.
     * <ul>
     * <li>The distances are clipped to the actual line length
     * <li>If the start distance is equal to the end distance, a zero-length line with two identical points is returned
     * <li>FUTURE: If the start distance is greater than the end distance, an inverted section of the line is returned
     * </ul>
     * <p>
     * FUTURE: should handle startLength > endLength, and flip the returned linestring. Also should handle negative lengths
     * (they are measured from end of line backwards).
     */

    /**
     * @param line LineString; geometry
     * @param startLength double; start position
     * @param endLength double; end position
     * @return the substring
     */
    public static LineString getSubstring(final LineString line, final double startLength, final double endLength)
    {
        SubstringLine ls = new SubstringLine(line);
        return ls.getSubstring(startLength, endLength);
    }

    /** Line geometry description. */
    private LineString line;

    /**
     * @param line LineString; input a line geometry
     */
    public SubstringLine(final LineString line)
    {
        this.line = line;
    }

    /**
     * @param startDistance double; start position
     * @param endDistance double; end position
     * @return LineString
     */
    public LineString getSubstring(double startDistance, final double endDistance)
    {
        // future: if start > end, flip values and return an inverted line
        Assert.isTrue(startDistance <= endDistance, "inverted distances not currently supported");

        Coordinate[] coordinates = line.getCoordinates();
        // check for a zero-length segment and handle appropriately
        if (endDistance <= 0.0)
        {
            return line.getFactory().createLineString(new Coordinate[] { coordinates[0], coordinates[0] });
        }
        if (startDistance >= line.getLength())
        {
            return line.getFactory().createLineString(
                    new Coordinate[] { coordinates[coordinates.length - 1], coordinates[coordinates.length - 1] });
        }
        if (startDistance < 0.0)
        {
            startDistance = 0.0;
        }
        return computeSubstring(startDistance, endDistance);
    }

    /**
     * Assumes input is strictly valid (e.g. startDist < endDistance)
     * @param startDistance double; start position
     * @param endDistance double; end position
     * @return the substring
     */
    private LineString computeSubstring(final double startDistance, final double endDistance)
    {
        Coordinate[] coordinates = line.getCoordinates();
        CoordinateList newCoordinates = new CoordinateList();
        double segmentStartDistance = 0.0;
        double segmentEndDistance = 0.0;
        boolean started = false;
        int i = 0;
        LineSegment segment = new LineSegment();
        while (i < coordinates.length - 1 && endDistance > segmentEndDistance)
        {
            segment.p0 = coordinates[i];
            segment.p1 = coordinates[i + 1];
            i++;
            segmentStartDistance = segmentEndDistance;
            segmentEndDistance = segmentStartDistance + segment.getLength();

            if (startDistance > segmentEndDistance)
            {
                continue;
            }
            if (startDistance >= segmentStartDistance && startDistance < segmentEndDistance)
            {
                newCoordinates.add(LocatePoint.pointAlongSegment(segment.p0, segment.p1, startDistance - segmentStartDistance),
                        false);
            }
            /*
             * if (startDistance >= segmentStartDistance && startDistance == segmentEndDistance) { newCoordinates.add(new
             * Coordinate(segment.p1), false); }
             */
            if (endDistance >= segmentEndDistance)
            {
                newCoordinates.add(new Coordinate(segment.p1), false);
            }
            if (endDistance >= segmentStartDistance && endDistance < segmentEndDistance)
            {
                newCoordinates.add(LocatePoint.pointAlongSegment(segment.p0, segment.p1, endDistance - segmentStartDistance),
                        false);
            }
        }
        Coordinate[] newCoordinateArray = newCoordinates.toCoordinateArray();
        /**
         * Ensure there is enough coordinates to build a valid line. Make a 2-point line with duplicate coordinates, if
         * necessary There will always be at least one coordinate in the coordList.
         */
        if (newCoordinateArray.length <= 1)
        {
            newCoordinateArray = new Coordinate[] { newCoordinateArray[0], newCoordinateArray[0] };
        }
        return line.getFactory().createLineString(newCoordinateArray);
    }
}