View Javadoc
1   /**
2    *
3    */
4   package org.opentrafficsim.road.network.factory.shape;
5   
6   import org.locationtech.jts.geom.Coordinate;
7   import org.locationtech.jts.geom.CoordinateList;
8   import org.locationtech.jts.geom.LineSegment;
9   import org.locationtech.jts.geom.LineString;
10  import org.locationtech.jts.util.Assert;
11  
12  /**
13   * @author P070518
14   */
15  public class SubstringLine
16  {
17      /**
18       * Computes a substring of a {@link LineString} between given distances along the line.
19       * <ul>
20       * <li>The distances are clipped to the actual line length
21       * <li>If the start distance is equal to the end distance, a zero-length line with two identical points is returned
22       * <li>FUTURE: If the start distance is greater than the end distance, an inverted section of the line is returned
23       * </ul>
24       * <p>
25       * FUTURE: should handle startLength > endLength, and flip the returned linestring. Also should handle negative lengths
26       * (they are measured from end of line backwards).
27       */
28  
29      /**
30       * @param line LineString;
31       * @param startLength double;
32       * @param endLength double;
33       * @return the substring
34       */
35      public static LineString getSubstring(LineString line, double startLength, double endLength)
36      {
37          SubstringLine ls = new SubstringLine(line);
38          return ls.getSubstring(startLength, endLength);
39      }
40  
41      private LineString line;
42  
43      public SubstringLine(LineString line)
44      {
45          this.line = line;
46      }
47  
48      public LineString getSubstring(double startDistance, double endDistance)
49      {
50          // future: if start > end, flip values and return an inverted line
51          Assert.isTrue(startDistance <= endDistance, "inverted distances not currently supported");
52  
53          Coordinate[] coordinates = line.getCoordinates();
54          // check for a zero-length segment and handle appropriately
55          if (endDistance <= 0.0)
56          {
57              return line.getFactory().createLineString(new Coordinate[] {coordinates[0], coordinates[0]});
58          }
59          if (startDistance >= line.getLength())
60          {
61              return line.getFactory().createLineString(
62                      new Coordinate[] {coordinates[coordinates.length - 1], coordinates[coordinates.length - 1]});
63          }
64          if (startDistance < 0.0)
65          {
66              startDistance = 0.0;
67          }
68          return computeSubstring(startDistance, endDistance);
69      }
70  
71      /**
72       * Assumes input is strictly valid (e.g. startDist < endDistance)
73       * @param startDistance double;
74       * @param endDistance double;
75       * @return the substring
76       */
77      private LineString computeSubstring(double startDistance, double endDistance)
78      {
79          Coordinate[] coordinates = line.getCoordinates();
80          CoordinateList newCoordinates = new CoordinateList();
81          double segmentStartDistance = 0.0;
82          double segmentEndDistance = 0.0;
83          boolean started = false;
84          int i = 0;
85          LineSegment segment = new LineSegment();
86          while (i < coordinates.length - 1 && endDistance > segmentEndDistance)
87          {
88              segment.p0 = coordinates[i];
89              segment.p1 = coordinates[i + 1];
90              i++;
91              segmentStartDistance = segmentEndDistance;
92              segmentEndDistance = segmentStartDistance + segment.getLength();
93  
94              if (startDistance > segmentEndDistance)
95              {
96                  continue;
97              }
98              if (startDistance >= segmentStartDistance && startDistance < segmentEndDistance)
99              {
100                 newCoordinates.add(LocatePoint.pointAlongSegment(segment.p0, segment.p1, startDistance - segmentStartDistance),
101                         false);
102             }
103             /*
104              * if (startDistance >= segmentStartDistance && startDistance == segmentEndDistance) { newCoordinates.add(new
105              * Coordinate(segment.p1), false); }
106              */
107             if (endDistance >= segmentEndDistance)
108             {
109                 newCoordinates.add(new Coordinate(segment.p1), false);
110             }
111             if (endDistance >= segmentStartDistance && endDistance < segmentEndDistance)
112             {
113                 newCoordinates.add(LocatePoint.pointAlongSegment(segment.p0, segment.p1, endDistance - segmentStartDistance),
114                         false);
115             }
116         }
117         Coordinate[] newCoordinateArray = newCoordinates.toCoordinateArray();
118         /**
119          * Ensure there is enough coordinates to build a valid line. Make a 2-point line with duplicate coordinates, if
120          * necessary There will always be at least one coordinate in the coordList.
121          */
122         if (newCoordinateArray.length <= 1)
123         {
124             newCoordinateArray = new Coordinate[] {newCoordinateArray[0], newCoordinateArray[0]};
125         }
126         return line.getFactory().createLineString(newCoordinateArray);
127     }
128 }