Class OffsetCurveBuilder

  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 package org.locationtech.jts.operation.buffer;
 13  
 14 import org.locationtech.jts.geom.Coordinate;
 15 import org.locationtech.jts.geom.CoordinateArrays;
 16 import org.locationtech.jts.geom.Geometry;
 17 import org.locationtech.jts.geom.PrecisionModel;
 18 import org.locationtech.jts.geomgraph.Position;
 19  
 20 /**
 21  * Computes the raw offset curve for a
 22  * single {@link Geometry} component (ring, line or point).
 23  * A raw offset curve line is not noded -
 24  * it may contain self-intersections (and usually will).
 25  * The final buffer polygon is computed by forming a topological graph
 26  * of all the noded raw curves and tracing outside contours.
 27  * The points in the raw curve are rounded 
 28  * to a given {@link PrecisionModel}.
 29  *
 30  * @version 1.7
 31  */
 32 public class OffsetCurveBuilder 
 33 {  
 34   private double distance = 0.0;
 35   private PrecisionModel precisionModel;
 36   private BufferParameters bufParams;
 37   
 38   public OffsetCurveBuilder(
 39                 PrecisionModel precisionModel,
 40                 BufferParameters bufParams
 41                 )
 42   {
 43     this.precisionModel = precisionModel;
 44     this.bufParams = bufParams;
 45   }
 46  
 47   /**
 48    * Gets the buffer parameters being used to generate the curve.
 49    * 
 50    * @return the buffer parameters being used
 51    */
 52   public BufferParameters getBufferParameters()
 53   {
 54     return bufParams;
 55   }
 56   
 57   /**
 58    * This method handles single points as well as LineStrings.
 59    * LineStrings are assumed <b>not</b> to be closed (the function will not
 60    * fail for closed lines, but will generate superfluous line caps).
 61    *
 62    * @param inputPts the vertices of the line to offset
 63    * @param distance the offset distance
 64    * 
 65    * @return a Coordinate array representing the curve
 66    * or null if the curve is empty
 67    */
 68   public Coordinate[] getLineCurve(Coordinate[] inputPts, double distance)
 69   {
 70     this.distance = distance;
 71     
 72     if (isLineOffsetEmpty(distance)) return null;
 73  
 74     double posDistance = Math.abs(distance);
 75     OffsetSegmentGenerator segGen = getSegGen(posDistance);
 76     if (inputPts.length <= 1) {
 77       computePointCurve(inputPts[0], segGen);
 78     }
 79     else {
 80       if (bufParams.isSingleSided()) {
 81         boolean isRightSide = distance < 0.0;
 82         computeSingleSidedBufferCurve(inputPts, isRightSide, segGen);
 83       }
 84       else
 85         computeLineBufferCurve(inputPts, segGen);
 86     }
 87     
 88     Coordinate[] lineCoord = segGen.getCoordinates();
 89     return lineCoord;
 90   }
 91  
 92   /**
 93    * Tests whether the offset curve for line or point geometries
 94    * at the given offset distance is empty (does not exist).
 95    * This is the case if:
 96    * <ul>
 97    * <li>the distance is zero, 
 98    * <li>the distance is negative, except for the case of singled-sided buffers
 99    * </ul>
100    * 
101    * @param distance the offset curve distance
102    * @return true if the offset curve is empty
103    */
104   public boolean isLineOffsetEmpty(double distance) {
105     // a zero width buffer of a line or point is empty
106     if (distance == 0.0return true;
107     // a negative width buffer of a line or point is empty,
108     // except for single-sided buffers, where the sign indicates the side
109     if (distance < 0.0 && ! bufParams.isSingleSided()) return true;
110     return false;
111   }
112  
113   /**
114    * This method handles the degenerate cases of single points and lines,
115    * as well as valid rings.
116    *
117    * @param inputPts the coordinates of the ring (must not contain repeated points)
118    * @param side side the side {@link Position} of the ring on which to construct the buffer line
119    * @param distance the positive distance at which to create the offset
120    * @return a Coordinate array representing the curve,
121    * or null if the curve is empty
122    */
123   public Coordinate[] getRingCurve(Coordinate[] inputPts, int side, double distance)
124   {
125     this.distance = distance;
126     if (inputPts.length <= 2)
127       return getLineCurve(inputPts, distance);
128  
129     // optimize creating ring for for zero distance
130     if (distance == 0.0) {
131       return copyCoordinates(inputPts);
132     }
133     OffsetSegmentGenerator segGen = getSegGen(distance);
134     computeRingBufferCurve(inputPts, side, segGen);
135     return segGen.getCoordinates();
136   }
137  
138   public Coordinate[] getOffsetCurve(Coordinate[] inputPts, double distance)
139   {
140     this.distance = distance;
141     
142     // a zero width offset curve is empty
143     if (distance == 0.0return null;
144  
145     boolean isRightSide = distance < 0.0;
146     double posDistance = Math.abs(distance);
147     OffsetSegmentGenerator segGen = getSegGen(posDistance);
148     if (inputPts.length <= 1) {
149       computePointCurve(inputPts[0], segGen);
150     }
151     else {
152       computeOffsetCurve(inputPts, isRightSide, segGen);
153     }
154     Coordinate[] curvePts = segGen.getCoordinates();
155     // for right side line is traversed in reverse direction, so have to reverse generated line
156     if (isRightSide) 
157       CoordinateArrays.reverse(curvePts);
158     return curvePts;
159   }
160  
161   private static Coordinate[] copyCoordinates(Coordinate[] pts)
162   {
163     Coordinate[] copy = new Coordinate[pts.length];
164     for (int i = 0; i < copy.length; i++) {
165       copy[i] = new Coordinate(pts[i]);
166     }
167     return copy;
168   }
169     
170   private OffsetSegmentGenerator getSegGen(double distance)
171   {
172     return new OffsetSegmentGenerator(precisionModel, bufParams, distance);
173   }
174   
175   /**
176    * Computes the distance tolerance to use during input
177    * line simplification.
178    * 
179    * @param distance the buffer distance
180    * @return the simplification tolerance
181    */
182   private double simplifyTolerance(double bufDistance)
183   {
184     return bufDistance * bufParams.getSimplifyFactor();
185   }
186   
187   private void computePointCurve(Coordinate pt, OffsetSegmentGenerator segGen) {
188     switch (bufParams.getEndCapStyle()) {
189       case BufferParameters.CAP_ROUND:
190         segGen.createCircle(pt);
191         break;
192       case BufferParameters.CAP_SQUARE:
193         segGen.createSquare(pt);
194         break;
195       // otherwise curve is empty (e.g. for a butt cap);
196     }
197   }
198  
199   private void computeLineBufferCurve(Coordinate[] inputPts, OffsetSegmentGenerator segGen)
200   {
201     double distTol = simplifyTolerance(distance);
202     
203     //--------- compute points for left side of line
204     // Simplify the appropriate side of the line before generating
205     Coordinate[] simp1 = BufferInputLineSimplifier.simplify(inputPts, distTol);
206     // MD - used for testing only (to eliminate simplification)
207 //    Coordinate[] simp1 = inputPts;
208     
209     int n1 = simp1.length - 1;
210     segGen.initSideSegments(simp1[0], simp1[1], Position.LEFT);
211     for (int i = 2; i <= n1; i++) {
212       segGen.addNextSegment(simp1[i], true);
213     }
214     segGen.addLastSegment();
215     // add line cap for end of line
216     segGen.addLineEndCap(simp1[n1 - 1], simp1[n1]);
217     
218     //---------- compute points for right side of line
219     // Simplify the appropriate side of the line before generating
220     Coordinate[] simp2 = BufferInputLineSimplifier.simplify(inputPts, -distTol);
221     // MD - used for testing only (to eliminate simplification)
222 //    Coordinate[] simp2 = inputPts;
223     int n2 = simp2.length - 1;
224    
225     // since we are traversing line in opposite order, offset position is still LEFT
226     segGen.initSideSegments(simp2[n2], simp2[n2 - 1], Position.LEFT);
227     for (int i = n2 - 2; i >= 0; i--) {
228       segGen.addNextSegment(simp2[i], true);
229     }
230     segGen.addLastSegment();
231     // add line cap for start of line
232     segGen.addLineEndCap(simp2[1], simp2[0]);
233  
234     segGen.closeRing();
235   }
236   
237   private void computeSingleSidedBufferCurve(Coordinate[] inputPts, boolean isRightSide, OffsetSegmentGenerator segGen)
238   {
239     double distTol = simplifyTolerance(distance);
240     
241     if (isRightSide) {
242       // add original line
243       segGen.addSegments(inputPts, true);
244       
245       //---------- compute points for right side of line
246       // Simplify the appropriate side of the line before generating
247       Coordinate[] simp2 = BufferInputLineSimplifier.simplify(inputPts, -distTol);
248       // MD - used for testing only (to eliminate simplification)
249   //    Coordinate[] simp2 = inputPts;
250       int n2 = simp2.length - 1;
251      
252       // since we are traversing line in opposite order, offset position is still LEFT
253       segGen.initSideSegments(simp2[n2], simp2[n2 - 1], Position.LEFT);
254       segGen.addFirstSegment();
255       for (int i = n2 - 2; i >= 0; i--) {
256         segGen.addNextSegment(simp2[i], true);
257       }
258     }
259     else {
260       // add original line
261       segGen.addSegments(inputPts, false);
262       
263       //--------- compute points for left side of line
264       // Simplify the appropriate side of the line before generating
265       Coordinate[] simp1 = BufferInputLineSimplifier.simplify(inputPts, distTol);
266       // MD - used for testing only (to eliminate simplification)
267 //      Coordinate[] simp1 = inputPts;
268       
269       int n1 = simp1.length - 1;
270       segGen.initSideSegments(simp1[0], simp1[1], Position.LEFT);
271       segGen.addFirstSegment();
272       for (int i = 2; i <= n1; i++) {
273         segGen.addNextSegment(simp1[i], true);
274       }
275     }
276     segGen.addLastSegment();
277     segGen.closeRing();
278   }
279  
280   private void computeOffsetCurve(Coordinate[] inputPts, boolean isRightSide, OffsetSegmentGenerator segGen)
281   {
282     double distTol = simplifyTolerance(distance);
283     
284     if (isRightSide) {
285       //---------- compute points for right side of line
286       // Simplify the appropriate side of the line before generating
287       Coordinate[] simp2 = BufferInputLineSimplifier.simplify(inputPts, -distTol);
288       // MD - used for testing only (to eliminate simplification)
289   //    Coordinate[] simp2 = inputPts;
290       int n2 = simp2.length - 1;
291      
292       // since we are traversing line in opposite order, offset position is still LEFT
293       segGen.initSideSegments(simp2[n2], simp2[n2 - 1], Position.LEFT);
294       segGen.addFirstSegment();
295       for (int i = n2 - 2; i >= 0; i--) {
296         segGen.addNextSegment(simp2[i], true);
297       }
298     }
299     else {
300       //--------- compute points for left side of line
301       // Simplify the appropriate side of the line before generating
302       Coordinate[] simp1 = BufferInputLineSimplifier.simplify(inputPts, distTol);
303       // MD - used for testing only (to eliminate simplification)
304 //      Coordinate[] simp1 = inputPts;
305       
306       int n1 = simp1.length - 1;
307       segGen.initSideSegments(simp1[0], simp1[1], Position.LEFT);
308       segGen.addFirstSegment();
309       for (int i = 2; i <= n1; i++) {
310         segGen.addNextSegment(simp1[i], true);
311       }
312     }
313     segGen.addLastSegment();
314   }
315  
316   private void computeRingBufferCurve(Coordinate[] inputPts, int side, OffsetSegmentGenerator segGen)
317   {
318     // simplify input line to improve performance
319     double distTol = simplifyTolerance(distance);
320     // ensure that correct side is simplified
321     if (side == Position.RIGHT)
322       distTol = -distTol;
323     Coordinate[] simp = BufferInputLineSimplifier.simplify(inputPts, distTol);
324 //    Coordinate[] simp = inputPts;
325     
326     int n = simp.length - 1;
327     segGen.initSideSegments(simp[n - 1], simp[0], side);
328     for (int i = 1; i <= n; i++) {
329       boolean addStartPoint = i != 1;
330       segGen.addNextSegment(simp[i], addStartPoint);
331     }
332     segGen.closeRing();
333   }
334  
335  
336 }
337