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