Class LinearLocation

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12  
 13 package org.locationtech.jts.linearref;
 14  
 15 import org.locationtech.jts.geom.Coordinate;
 16 import org.locationtech.jts.geom.Geometry;
 17 import org.locationtech.jts.geom.LineSegment;
 18 import org.locationtech.jts.geom.LineString;
 19 import org.locationtech.jts.geom.MultiLineString;
 20  
 21 /**
 22  * Represents a location along a {@link LineString} or {@link MultiLineString}.
 23  * The referenced geometry is not maintained within
 24  * this location, but must be provided for operations which require it.
 25  * Various methods are provided to manipulate the location value
 26  * and query the geometry it references.
 27  */
 28 public class LinearLocation
 29     implements Comparable
 30 {
 31    /**
 32     * Gets a location which refers to the end of a linear {@link Geometry}.
 33     * @param linear the linear geometry
 34     * @return a new <tt>LinearLocation</tt>
 35     */
 36   public static LinearLocation getEndLocation(Geometry linear)
 37   {
 38     // assert: linear is LineString or MultiLineString
 39     LinearLocation loc = new LinearLocation();
 40     loc.setToEnd(linear);
 41     return loc;
 42   }
 43  
 44   /**
 45    * Computes the {@link Coordinate} of a point a given fraction
 46    * along the line segment <tt>(p0, p1)</tt>.
 47    * If the fraction is greater than 1.0 the last
 48    * point of the segment is returned.
 49    * If the fraction is less than or equal to 0.0 the first point
 50    * of the segment is returned.
 51    * The Z ordinate is interpolated from the Z-ordinates of the given points,
 52    * if they are specified.
 53    *
 54    * @param p0 the first point of the line segment
 55    * @param p1 the last point of the line segment
 56    * @param frac the length to the desired point
 57    * @return the <tt>Coordinate</tt> of the desired point
 58    */
 59   public static Coordinate pointAlongSegmentByFraction(Coordinate p0, Coordinate p1, double frac)
 60   {
 61     if (frac <= 0.0return p0;
 62     if (frac >= 1.0return p1;
 63  
 64     double x = (p1.x - p0.x) * frac + p0.x;
 65     double y = (p1.y - p0.y) * frac + p0.y;
 66     // interpolate Z value. If either input Z is NaN, result z will be NaN as well.
 67     double z = (p1.getZ() - p0.getZ()) * frac + p0.getZ();
 68     return new Coordinate(x, y, z);
 69   }
 70  
 71   private int componentIndex = 0;
 72   private int segmentIndex = 0;
 73   private double segmentFraction = 0.0;
 74  
 75   /**
 76    * Creates a location referring to the start of a linear geometry
 77    */
 78   public LinearLocation()
 79   {
 80   }
 81  
 82   public LinearLocation(int segmentIndex, double segmentFraction) {
 83     this(0, segmentIndex, segmentFraction);
 84   }
 85  
 86   public LinearLocation(int componentIndex, int segmentIndex, double segmentFraction)
 87   {
 88     this.componentIndex = componentIndex;
 89     this.segmentIndex = segmentIndex;
 90     this.segmentFraction = segmentFraction;
 91     normalize();
 92   }
 93  
 94   private LinearLocation(int componentIndex, int segmentIndex, double segmentFraction, boolean doNormalize)
 95   {
 96     this.componentIndex = componentIndex;
 97     this.segmentIndex = segmentIndex;
 98     this.segmentFraction = segmentFraction;
 99     if (doNormalize) 
100       normalize();
101   }
102  
103   /**
104    * Creates a new location equal to a given one.
105    * 
106    * @param loc a LinearLocation
107    */
108   public LinearLocation(LinearLocation loc)
109   {
110     this.componentIndex = loc.componentIndex;
111     this.segmentIndex = loc.segmentIndex;
112     this.segmentFraction = loc.segmentFraction;
113   }
114  
115   /**
116    * Ensures the individual values are locally valid.
117    * Does <b>not</b> ensure that the indexes are valid for
118    * a particular linear geometry.
119    *
120    * @see clamp
121    */
122   private void normalize()
123   {
124     if (segmentFraction < 0.0) {
125       segmentFraction = 0.0;
126     }
127     if (segmentFraction > 1.0) {
128       segmentFraction = 1.0;
129     }
130  
131     if (componentIndex < 0) {
132       componentIndex = 0;
133       segmentIndex = 0;
134       segmentFraction = 0.0;
135     }
136     if (segmentIndex < 0) {
137       segmentIndex = 0;
138       segmentFraction = 0.0;
139     }
140     if (segmentFraction == 1.0) {
141       segmentFraction = 0.0;
142       segmentIndex += 1;
143     }
144   }
145  
146  
147   /**
148    * Ensures the indexes are valid for a given linear {@link Geometry}.
149    *
150    * @param linear a linear geometry
151    */
152   public void clamp(Geometry linear)
153   {
154     if (componentIndex >= linear.getNumGeometries()) {
155       setToEnd(linear);
156       return;
157     }
158     if (segmentIndex >= linear.getNumPoints()) {
159       LineString line = (LineString) linear.getGeometryN(componentIndex);
160       segmentIndex = numSegments(line);
161       segmentFraction = 1.0;
162     }
163   }
164   /**
165    * Snaps the value of this location to
166    * the nearest vertex on the given linear {@link Geometry},
167    * if the vertex is closer than <tt>minDistance</tt>.
168    *
169    * @param linearGeom a linear geometry
170    * @param minDistance the minimum allowable distance to a vertex
171    */
172   public void snapToVertex(Geometry linearGeom, double minDistance)
173   {
174     if (segmentFraction <= 0.0 || segmentFraction >= 1.0)
175       return;
176     double segLen = getSegmentLength(linearGeom);
177     double lenToStart = segmentFraction * segLen;
178     double lenToEnd = segLen - lenToStart;
179     if (lenToStart <= lenToEnd && lenToStart < minDistance) {
180       segmentFraction = 0.0;
181     }
182     else if (lenToEnd <= lenToStart && lenToEnd < minDistance) {
183       segmentFraction = 1.0;
184     }
185   }
186  
187   /**
188    * Gets the length of the segment in the given
189    * Geometry containing this location.
190    *
191    * @param linearGeom a linear geometry
192    * @return the length of the segment
193    */
194   public double getSegmentLength(Geometry linearGeom)
195   {
196     LineString lineComp = (LineString) linearGeom.getGeometryN(componentIndex);
197  
198     // ensure segment index is valid
199     int segIndex = segmentIndex;
200     if (segmentIndex >= numSegments(lineComp))
201       segIndex = lineComp.getNumPoints() - 2;
202  
203     Coordinate p0 = lineComp.getCoordinateN(segIndex);
204     Coordinate p1 = lineComp.getCoordinateN(segIndex + 1);
205     return p0.distance(p1);
206   }
207  
208   /**
209    * Sets the value of this location to
210    * refer to the end of a linear geometry.
211    *
212    * @param linear the linear geometry to use to set the end
213    */
214   public void setToEnd(Geometry linear)
215   {
216     componentIndex = linear.getNumGeometries() - 1;
217     LineString lastLine = (LineString) linear.getGeometryN(componentIndex);
218     segmentIndex = numSegments(lastLine);
219     segmentFraction = 0.0;
220   }
221  
222   /**
223    * Gets the component index for this location.
224    *
225    * @return the component index
226    */
227   public int getComponentIndex() { return componentIndex; }
228  
229   /**
230    * Gets the segment index for this location
231    *
232    * @return the segment index
233    */
234   public int getSegmentIndex() { return segmentIndex; }
235  
236   /**
237    * Gets the segment fraction for this location
238    *
239    * @return the segment fraction
240    */
241   public double getSegmentFraction() { return segmentFraction; }
242  
243   /**
244    * Tests whether this location refers to a vertex
245    *
246    * @return true if the location is a vertex
247    */
248   public boolean isVertex()
249   {
250     return segmentFraction <= 0.0 || segmentFraction >= 1.0;
251   }
252  
253   /**
254    * Gets the {@link Coordinate} along the
255    * given linear {@link Geometry} which is
256    * referenced by this location.
257    *
258    * @param linearGeom the linear geometry referenced by this location
259    * @return the <tt>Coordinate</tt> at the location
260    */
261   public Coordinate getCoordinate(Geometry linearGeom)
262   {
263     LineString lineComp = (LineString) linearGeom.getGeometryN(componentIndex);
264     Coordinate p0 = lineComp.getCoordinateN(segmentIndex);
265     if (segmentIndex >= numSegments(lineComp))
266       return p0;
267     Coordinate p1 = lineComp.getCoordinateN(segmentIndex + 1);
268     return pointAlongSegmentByFraction(p0, p1, segmentFraction);
269   }
270  
271   /**
272    * Gets a {@link LineSegment} representing the segment of the 
273    * given linear {@link Geometry} which contains this location.
274    *
275    * @param linearGeom a linear geometry
276    * @return the <tt>LineSegment</tt> containing the location
277    */
278   public LineSegment getSegment(Geometry linearGeom)
279   {
280     LineString lineComp = (LineString) linearGeom.getGeometryN(componentIndex);
281     Coordinate p0 = lineComp.getCoordinateN(segmentIndex);
282     // check for endpoint - return last segment of the line if so
283     if (segmentIndex >= numSegments(lineComp)) {
284         Coordinate prev = lineComp.getCoordinateN(lineComp.getNumPoints() - 2);
285       return new LineSegment(prev, p0);
286     }
287     Coordinate p1 = lineComp.getCoordinateN(segmentIndex + 1);
288     return new LineSegment(p0, p1);
289   }
290  
291   /**
292    * Tests whether this location refers to a valid
293    * location on the given linear {@link Geometry}.
294    *
295    * @param linearGeom a linear geometry
296    * @return true if this location is valid
297    */
298   public boolean isValid(Geometry linearGeom)
299   {
300     if (componentIndex < 0 || componentIndex >= linearGeom.getNumGeometries())
301       return false;
302  
303     LineString lineComp = (LineString) linearGeom.getGeometryN(componentIndex);
304     if (segmentIndex < 0 || segmentIndex > lineComp.getNumPoints())
305       return false;
306     if (segmentIndex == lineComp.getNumPoints() && segmentFraction != 0.0)
307       return false;
308  
309     if (segmentFraction < 0.0 || segmentFraction > 1.0)
310       return false;
311     return true;
312   }
313  
314   /**
315    *  Compares this object with the specified object for order.
316    *
317    *@param  o  the <code>LineStringLocation</code> with which this <code>Coordinate</code>
318    *      is being compared
319    *@return    a negative integer, zero, or a positive integer as this <code>LineStringLocation</code>
320    *      is less than, equal to, or greater than the specified <code>LineStringLocation</code>
321    */
322   public int compareTo(Object o) {
323     LinearLocation other = (LinearLocation) o;
324     // compare component indices
325     if (componentIndex < other.componentIndex) return -1;
326     if (componentIndex > other.componentIndex) return 1;
327     // compare segments
328     if (segmentIndex < other.segmentIndex) return -1;
329     if (segmentIndex > other.segmentIndex) return 1;
330     // same segment, so compare segment fraction
331     if (segmentFraction < other.segmentFraction) return -1;
332     if (segmentFraction > other.segmentFraction) return 1;
333     // same location
334     return 0;
335   }
336  
337   /**
338    *  Compares this object with the specified index values for order.
339    *
340    * @param componentIndex1 a component index
341    * @param segmentIndex1 a segment index
342    * @param segmentFraction1 a segment fraction
343    * @return    a negative integer, zero, or a positive integer as this <code>LineStringLocation</code>
344    *      is less than, equal to, or greater than the specified locationValues
345    */
346   public int compareLocationValues(int componentIndex1, int segmentIndex1, double segmentFraction1) {
347     // compare component indices
348     if (componentIndex < componentIndex1) return -1;
349     if (componentIndex > componentIndex1) return 1;
350     // compare segments
351     if (segmentIndex < segmentIndex1) return -1;
352     if (segmentIndex > segmentIndex1) return 1;
353     // same segment, so compare segment fraction
354     if (segmentFraction < segmentFraction1) return -1;
355     if (segmentFraction > segmentFraction1) return 1;
356     // same location
357     return 0;
358   }
359  
360   /**
361    *  Compares two sets of location values for order.
362    *
363    * @param componentIndex0 a component index
364    * @param segmentIndex0 a segment index
365    * @param segmentFraction0 a segment fraction
366    * @param componentIndex1 another component index
367    * @param segmentIndex1 another segment index
368    * @param segmentFraction1 another segment fraction
369    *@return    a negative integer, zero, or a positive integer
370    *      as the first set of location values
371    *      is less than, equal to, or greater than the second set of locationValues
372    */
373   public static int compareLocationValues(
374       int componentIndex0, int segmentIndex0, double segmentFraction0,
375       int componentIndex1, int segmentIndex1, double segmentFraction1)
376   {
377     // compare component indices
378     if (componentIndex0 < componentIndex1) return -1;
379     if (componentIndex0 > componentIndex1) return 1;
380     // compare segments
381     if (segmentIndex0 < segmentIndex1) return -1;
382     if (segmentIndex0 > segmentIndex1) return 1;
383     // same segment, so compare segment fraction
384     if (segmentFraction0 < segmentFraction1) return -1;
385     if (segmentFraction0 > segmentFraction1) return 1;
386     // same location
387     return 0;
388   }
389  
390   /**
391    * Tests whether two locations
392    * are on the same segment in the parent {@link Geometry}.
393    * 
394    * @param loc a location on the same geometry
395    * @return true if the locations are on the same segment of the parent geometry
396    */
397   public boolean isOnSameSegment(LinearLocation loc)
398   {
399       if (componentIndex != loc.componentIndex) return false;
400       if (segmentIndex == loc.segmentIndex) return true;
401       if (loc.segmentIndex - segmentIndex == 1 
402               && loc.segmentFraction == 0.0
403           return true;
404       if (segmentIndex - loc.segmentIndex == 1 
405               && segmentFraction == 0.0
406           return true;
407       return false;
408   }
409  
410   /**
411    * Tests whether this location is an endpoint of
412    * the linear component it refers to.
413    * 
414    * @param linearGeom the linear geometry referenced by this location
415    * @return true if the location is a component endpoint
416    */
417   public boolean isEndpoint(Geometry linearGeom)
418   {
419     LineString lineComp = (LineString) linearGeom.getGeometryN(componentIndex);
420     // check for endpoint
421     int nseg = numSegments(lineComp);
422     return segmentIndex >= nseg
423         || (segmentIndex == nseg - 1 && segmentFraction >= 1.0);
424   }
425  
426   /**
427    * Converts a linear location to the lowest equivalent location index.
428    * The lowest index has the lowest possible component and segment indices.
429    * <p>
430    * Specifically:
431    * <ul>
432    * <li>if the location point is an endpoint, a location value is returned as (nseg-1, 1.0)
433    * <li>if the location point is ambiguous (i.e. an endpoint and a startpoint), the lowest endpoint location is returned
434    * </ul>
435    * If the location index is already the lowest possible value, the original location is returned.
436    * 
437    * @param linearGeom the linear geometry referenced by this location
438    * @return the lowest equivalent location
439    */
440   public LinearLocation toLowest(Geometry linearGeom)
441   {
442     // TODO: compute lowest component index
443     LineString lineComp = (LineString) linearGeom.getGeometryN(componentIndex);
444     int nseg = numSegments(lineComp);
445     // if not an endpoint can be returned directly
446     if (segmentIndex < nseg) return this;
447     return new LinearLocation(componentIndex, nseg - 11.0false);
448   }
449   
450   /**
451    * Copies this location
452    *
453    * @return a copy of this location
454    * @deprecated
455    */
456   public Object clone()
457   {
458     return copy();
459   }
460   
461   /**
462    * Copies this location
463    *
464    * @return a copy of this location
465    */
466   public LinearLocation copy() {
467     return new LinearLocation(componentIndex, segmentIndex, segmentFraction);
468   }
469   
470   public String toString()
471   {
472     return "LinearLoc[" 
473     + componentIndex + ", "
474     + segmentIndex + ", "
475     + segmentFraction + "]";
476   }
477   
478   /**
479    * Gets the count of the number of line segments
480    * in a {@link LineString}.  This is one less than the 
481    * number of coordinates.
482    * 
483    * @param line a LineString
484    * @return the number of segments
485    */
486   private static int numSegments(LineString line) {
487     int npts = line.getNumPoints();
488     if (npts <= 1return 0;
489     return npts - 1;
490   }
491 }
492