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      /**
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; geometry
31       * @param startLength double; start position
32       * @param endLength double; end position
33       * @return the substring
34       */
35      public static LineString getSubstring(final LineString line, final double startLength, final double endLength)
36      {
37          SubstringLine ls = new SubstringLine(line);
38          return ls.getSubstring(startLength, endLength);
39      }
40  
41      /** Line geometry description. */
42      private LineString line;
43  
44      /**
45       * @param line LineString; input a line geometry
46       */
47      public SubstringLine(final LineString line)
48      {
49          this.line = line;
50      }
51  
52      /**
53       * @param startDistance double; start position
54       * @param endDistance double; end position
55       * @return LineString
56       */
57      public LineString getSubstring(double startDistance, final double endDistance)
58      {
59          // future: if start > end, flip values and return an inverted line
60          Assert.isTrue(startDistance <= endDistance, "inverted distances not currently supported");
61  
62          Coordinate[] coordinates = line.getCoordinates();
63          // check for a zero-length segment and handle appropriately
64          if (endDistance <= 0.0)
65          {
66              return line.getFactory().createLineString(new Coordinate[] { coordinates[0], coordinates[0] });
67          }
68          if (startDistance >= line.getLength())
69          {
70              return line.getFactory().createLineString(
71                      new Coordinate[] { coordinates[coordinates.length - 1], coordinates[coordinates.length - 1] });
72          }
73          if (startDistance < 0.0)
74          {
75              startDistance = 0.0;
76          }
77          return computeSubstring(startDistance, endDistance);
78      }
79  
80      /**
81       * Assumes input is strictly valid (e.g. startDist < endDistance)
82       * @param startDistance double; start position
83       * @param endDistance double; end position
84       * @return the substring
85       */
86      private LineString computeSubstring(final double startDistance, final double endDistance)
87      {
88          Coordinate[] coordinates = line.getCoordinates();
89          CoordinateList newCoordinates = new CoordinateList();
90          double segmentStartDistance = 0.0;
91          double segmentEndDistance = 0.0;
92          boolean started = false;
93          int i = 0;
94          LineSegment segment = new LineSegment();
95          while (i < coordinates.length - 1 && endDistance > segmentEndDistance)
96          {
97              segment.p0 = coordinates[i];
98              segment.p1 = coordinates[i + 1];
99              i++;
100             segmentStartDistance = segmentEndDistance;
101             segmentEndDistance = segmentStartDistance + segment.getLength();
102 
103             if (startDistance > segmentEndDistance)
104             {
105                 continue;
106             }
107             if (startDistance >= segmentStartDistance && startDistance < segmentEndDistance)
108             {
109                 newCoordinates.add(LocatePoint.pointAlongSegment(segment.p0, segment.p1, startDistance - segmentStartDistance),
110                         false);
111             }
112             /*
113              * if (startDistance >= segmentStartDistance && startDistance == segmentEndDistance) { newCoordinates.add(new
114              * Coordinate(segment.p1), false); }
115              */
116             if (endDistance >= segmentEndDistance)
117             {
118                 newCoordinates.add(new Coordinate(segment.p1), false);
119             }
120             if (endDistance >= segmentStartDistance && endDistance < segmentEndDistance)
121             {
122                 newCoordinates.add(LocatePoint.pointAlongSegment(segment.p0, segment.p1, endDistance - segmentStartDistance),
123                         false);
124             }
125         }
126         Coordinate[] newCoordinateArray = newCoordinates.toCoordinateArray();
127         /**
128          * Ensure there is enough coordinates to build a valid line. Make a 2-point line with duplicate coordinates, if
129          * necessary There will always be at least one coordinate in the coordList.
130          */
131         if (newCoordinateArray.length <= 1)
132         {
133             newCoordinateArray = new Coordinate[] { newCoordinateArray[0], newCoordinateArray[0] };
134         }
135         return line.getFactory().createLineString(newCoordinateArray);
136     }
137 }