Class LengthLocationMap

  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  
 18 /**
 19  * Computes the {@link LinearLocation} for a given length
 20  * along a linear {@link Geometry}.
 21  * Negative lengths are measured in reverse from end of the linear geometry.
 22  * Out-of-range values are clamped.
 23  */
 24 public class LengthLocationMap
 25 {
 26   // TODO: cache computed cumulative length for each vertex
 27   // TODO: support user-defined measures
 28   // TODO: support measure index for fast mapping to a location
 29  
 30   /**
 31    * Computes the {@link LinearLocation} for a
 32    * given length along a linear {@link Geometry}.
 33    *
 34    * @param linearGeom the linear geometry to use
 35    * @param length the length index of the location
 36    * @return the {@link LinearLocation} for the length
 37    */
 38   public static LinearLocation getLocation(Geometry linearGeom, double length)
 39   {
 40     LengthLocationMap locater = new LengthLocationMap(linearGeom);
 41     return locater.getLocation(length);
 42   }
 43  
 44   /**
 45    * Computes the {@link LinearLocation} for a
 46    * given length along a linear {@link Geometry},
 47    * with control over how the location
 48    * is resolved at component endpoints.
 49    *
 50    * @param linearGeom the linear geometry to use
 51    * @param length the length index of the location
 52    * @param resolveLower if true lengths are resolved to the lowest possible index
 53    * @return the {@link LinearLocation} for the length
 54    */
 55   public static LinearLocation getLocation(Geometry linearGeom, double length, boolean resolveLower)
 56   {
 57     LengthLocationMap locater = new LengthLocationMap(linearGeom);
 58     return locater.getLocation(length, resolveLower);
 59   }
 60  
 61   /**
 62    * Computes the length for a given {@link LinearLocation}
 63    * on a linear {@link Geometry}.
 64    *
 65    * @param linearGeom the linear geometry to use
 66    * @param loc the {@link LinearLocation} index of the location
 67    * @return the length for the {@link LinearLocation}
 68    */
 69   public static double getLength(Geometry linearGeom, LinearLocation loc)
 70   {
 71     LengthLocationMap locater = new LengthLocationMap(linearGeom);
 72     return locater.getLength(loc);
 73   }
 74  
 75   private Geometry linearGeom;
 76  
 77   public LengthLocationMap(Geometry linearGeom)
 78   {
 79     this.linearGeom = linearGeom;
 80   }
 81  
 82   /**
 83    * Compute the {@link LinearLocation} corresponding to a length.
 84    * Negative lengths are measured in reverse from end of the linear geometry.
 85    * Out-of-range values are clamped.
 86    * Ambiguous indexes are resolved to the lowest possible location value.
 87    *
 88    * @param length the length index
 89    * @return the corresponding LinearLocation
 90    */
 91   public LinearLocation getLocation(double length)
 92   {
 93     return getLocation(length, true);
 94   }
 95  
 96   /**
 97    * Compute the {@link LinearLocation} corresponding to a length.
 98    * Negative lengths are measured in reverse from end of the linear geometry.
 99    * Out-of-range values are clamped.
100    * Ambiguous indexes are resolved to the lowest or highest possible location value,
101    * depending on the value of <tt>resolveLower</tt>
102    *
103    * @param length the length index
104    * @return the corresponding LinearLocation
105    */
106   public LinearLocation getLocation(double length, boolean resolveLower)
107   {
108     double forwardLength = length;
109     
110     // negative values are measured from end of geometry
111     if (length < 0.0) {
112       double lineLen = linearGeom.getLength();
113       forwardLength = lineLen + length;
114     }
115     LinearLocation loc = getLocationForward(forwardLength);
116     if (resolveLower) {
117       return loc;
118     }
119     return resolveHigher(loc);
120   }
121  
122   private LinearLocation getLocationForward(double length)
123   {
124     if (length <= 0.0)
125       return new LinearLocation();
126  
127     double totalLength = 0.0;
128  
129     LinearIterator it = new LinearIterator(linearGeom);
130     while (it.hasNext()) {
131       
132       /**
133        * Special handling is required for the situation when the 
134        * length references exactly to a component endpoint.
135        * In this case, the endpoint location of the current component 
136        * is returned,
137        * rather than the startpoint location of the next component.
138        * This produces consistent behaviour with the project method.
139        */
140       if (it.isEndOfLine()) {
141         if (totalLength == length) {
142           int compIndex = it.getComponentIndex();
143           int segIndex = it.getVertexIndex();
144           return new LinearLocation(compIndex, segIndex, 0.0);          
145         }
146       }
147       else {
148         Coordinate p0 = it.getSegmentStart();
149         Coordinate p1 = it.getSegmentEnd();
150         double segLen = p1.distance(p0);
151         // length falls in this segment
152         if (totalLength + segLen > length) {
153           double frac = (length - totalLength) / segLen;
154           int compIndex = it.getComponentIndex();
155           int segIndex = it.getVertexIndex();
156           return new LinearLocation(compIndex, segIndex, frac);
157         }
158         totalLength += segLen;
159       }
160  
161       it.next();
162     }
163     // length is longer than line - return end location
164     return LinearLocation.getEndLocation(linearGeom);
165   }
166  
167   private LinearLocation resolveHigher(LinearLocation loc)
168   {
169     if (! loc.isEndpoint(linearGeom)) 
170       return loc;
171     int compIndex = loc.getComponentIndex();
172     // if last component can't resolve any higher
173     if (compIndex >= linearGeom.getNumGeometries() - 1return loc;
174  
175     do {
176       compIndex++;
177     } while (compIndex < linearGeom.getNumGeometries() - 1
178         && linearGeom.getGeometryN(compIndex).getLength() == 0);
179     // resolve to next higher location
180     return new LinearLocation(compIndex, 00.0); 
181   }
182   
183   public double getLength(LinearLocation loc)
184   {
185     double totalLength = 0.0;
186  
187     LinearIterator it = new LinearIterator(linearGeom);
188     while (it.hasNext()) {
189       if (! it.isEndOfLine()) {
190         Coordinate p0 = it.getSegmentStart();
191         Coordinate p1 = it.getSegmentEnd();
192         double segLen = p1.distance(p0);
193         // length falls in this segment
194         if (loc.getComponentIndex() == it.getComponentIndex()
195             && loc.getSegmentIndex() == it.getVertexIndex()) {
196           return totalLength + segLen * loc.getSegmentFraction();
197         }
198         totalLength += segLen;
199       }
200       it.next();
201     }
202     return totalLength;
203   }
204 }
205