001: /*
002: * The JTS Topology Suite is a collection of Java classes that
003: * implement the fundamental operations required to validate a given
004: * geo-spatial data set to a known topological specification.
005: *
006: * Copyright (C) 2001 Vivid Solutions
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public
019: * License along with this library; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021: *
022: * For more information, contact:
023: *
024: * Vivid Solutions
025: * Suite #1A
026: * 2328 Government Street
027: * Victoria BC V8T 5G5
028: * Canada
029: *
030: * (250)385-6040
031: * www.vividsolutions.com
032: */
033: package com.vividsolutions.jts.operation.buffer;
034:
035: /**
036: * @version 1.7
037: */
038: import com.vividsolutions.jts.geom.*;
039: import com.vividsolutions.jts.precision.SimpleGeometryPrecisionReducer;
040: import com.vividsolutions.jts.noding.*;
041: import com.vividsolutions.jts.noding.snapround.*;
042:
043: //import debug.*;
044:
045: /**
046: * Computes the buffer of a geometry, for both positive and negative buffer distances.
047: * <p>
048: * In GIS, the positive buffer of a geometry is defined as
049: * the Minkowski sum or difference of the geometry
050: * with a circle of radius equal to the absolute value of the buffer distance.
051: * In the CAD/CAM world buffers are known as </i>offset curves</i>.
052: * In morphological analysis they are known as <i>erosion</i> and <i>dilation</i>
053: * <p>
054: * The negative buffer of lines and points is always empty geometry.
055: * <p>
056: * Since true buffer curves may contain circular arcs,
057: * computed buffer polygons can only be approximations to the true geometry.
058: * The user can control the accuracy of the curve approximation by specifying
059: * the number of linear segments used to approximate curves.
060: * <p>
061: * The <b>end cap style</b> of a linear buffer may be specified. The
062: * following end cap styles are supported:
063: * <ul
064: * <li>{@link #CAP_ROUND} - the usual round end caps
065: * <li>{@link #CAP_BUTT} - end caps are truncated flat at the line ends
066: * <li>{@link #CAP_SQUARE} - end caps are squared off at the buffer distance beyond the line ends
067: * </ul>
068: * <p>
069: *
070: * @version 1.7
071: */
072: public class BufferOp {
073: /**
074: * Specifies a round line buffer end cap style.
075: */
076: public static final int CAP_ROUND = 1;
077: /**
078: * Specifies a butt (or flat) line buffer end cap style.
079: */
080: public static final int CAP_BUTT = 2;
081: /**
082: * Specifies a square line buffer end cap style.
083: */
084: public static final int CAP_SQUARE = 3;
085:
086: private static int MAX_PRECISION_DIGITS = 12;
087:
088: /**
089: * Compute a scale factor to limit the precision of
090: * a given combination of Geometry and buffer distance.
091: * The scale factor is determined by a combination of
092: * the number of digits of precision in the (geometry + buffer distance),
093: * limited by the supplied <code>maxPrecisionDigits</code> value.
094: *
095: * @param g the Geometry being buffered
096: * @param distance the buffer distance
097: * @param maxPrecisionDigits the max # of digits that should be allowed by
098: * the precision determined by the computed scale factor
099: *
100: * @return a scale factor for the buffer computation
101: */
102: private static double precisionScaleFactor(Geometry g,
103: double distance, int maxPrecisionDigits) {
104: Envelope env = g.getEnvelopeInternal();
105: double envSize = Math.max(env.getHeight(), env.getWidth());
106: double expandByDistance = distance > 0.0 ? distance : 0.0;
107: double bufEnvSize = envSize + 2 * expandByDistance;
108:
109: // the smallest power of 10 greater than the buffer envelope
110: int bufEnvLog10 = (int) (Math.log(bufEnvSize) / Math.log(10) + 1.0);
111: int minUnitLog10 = bufEnvLog10 - maxPrecisionDigits;
112: // scale factor is inverse of min Unit size, so flip sign of exponent
113: double scaleFactor = Math.pow(10.0, -minUnitLog10);
114: return scaleFactor;
115: }
116:
117: /**
118: * Computes the buffer of a geometry for a given buffer distance.
119: *
120: * @param g the geometry to buffer
121: * @param distance the buffer distance
122: * @return the buffer of the input geometry
123: */
124: public static Geometry bufferOp(Geometry g, double distance) {
125: BufferOp gBuf = new BufferOp(g);
126: Geometry geomBuf = gBuf.getResultGeometry(distance);
127: //BufferDebug.saveBuffer(geomBuf);
128: //BufferDebug.runCount++;
129: return geomBuf;
130: }
131:
132: /**
133: * Comutes the buffer for a geometry for a given buffer distance
134: * and accuracy of approximation.
135: *
136: * @param g the geometry to buffer
137: * @param distance the buffer distance
138: * @param quadrantSegments the number of segments used to approximate a quarter circle
139: * @return the buffer of the input geometry
140: *
141: */
142: public static Geometry bufferOp(Geometry g, double distance,
143: int quadrantSegments) {
144: BufferOp bufOp = new BufferOp(g);
145: bufOp.setQuadrantSegments(quadrantSegments);
146: Geometry geomBuf = bufOp.getResultGeometry(distance);
147: return geomBuf;
148: }
149:
150: /**
151: * Comutes the buffer for a geometry for a given buffer distance
152: * and accuracy of approximation.
153: *
154: * @param g the geometry to buffer
155: * @param distance the buffer distance
156: * @param quadrantSegments the number of segments used to approximate a quarter circle
157: * @param endCapStyle the end cap style to use
158: * @return the buffer of the input geometry
159: *
160: */
161: public static Geometry bufferOp(Geometry g, double distance,
162: int quadrantSegments, int endCapStyle) {
163: BufferOp bufOp = new BufferOp(g);
164: bufOp.setQuadrantSegments(quadrantSegments);
165: bufOp.setEndCapStyle(endCapStyle);
166: Geometry geomBuf = bufOp.getResultGeometry(distance);
167: return geomBuf;
168: }
169:
170: private Geometry argGeom;
171: private double distance;
172: private int quadrantSegments = OffsetCurveBuilder.DEFAULT_QUADRANT_SEGMENTS;
173: private int endCapStyle = BufferOp.CAP_ROUND;
174:
175: private Geometry resultGeometry = null;
176: private RuntimeException saveException; // debugging only
177:
178: /**
179: * Initializes a buffer computation for the given geometry
180: *
181: * @param g the geometry to buffer
182: */
183: public BufferOp(Geometry g) {
184: argGeom = g;
185: }
186:
187: /**
188: * Specifies the end cap style of the generated buffer.
189: * The styles supported are {@link #CAP_ROUND}, {@link #CAP_BUTT}, and {@link #CAP_SQUARE}.
190: * The default is CAP_ROUND.
191: *
192: * @param endCapStyle the end cap style to specify
193: */
194: public void setEndCapStyle(int endCapStyle) {
195: this .endCapStyle = endCapStyle;
196: }
197:
198: /**
199: * Sets the number of segments used to approximate a angle fillet
200: *
201: * @param quadrantSegments the number of segments in a fillet for a quadrant
202: */
203: public void setQuadrantSegments(int quadrantSegments) {
204: this .quadrantSegments = quadrantSegments;
205: }
206:
207: /**
208: * Returns the buffer computed for a geometry for a given buffer distance.
209: *
210: * @param distance the buffer distance
211: * @return the buffer of the input geometry
212: */
213: public Geometry getResultGeometry(double distance) {
214: this .distance = distance;
215: computeGeometry();
216: return resultGeometry;
217: }
218:
219: private void computeGeometry() {
220: bufferOriginalPrecision();
221: if (resultGeometry != null)
222: return;
223:
224: PrecisionModel argPM = argGeom.getFactory().getPrecisionModel();
225: if (argPM.getType() == PrecisionModel.FIXED)
226: bufferFixedPrecision(argPM);
227: else
228: bufferReducedPrecision();
229: }
230:
231: private void bufferReducedPrecision() {
232: // try and compute with decreasing precision
233: for (int precDigits = MAX_PRECISION_DIGITS; precDigits >= 0; precDigits--) {
234: try {
235: bufferReducedPrecision(precDigits);
236: } catch (TopologyException ex) {
237: saveException = ex;
238: // don't propagate the exception - it will be detected by fact that resultGeometry is null
239: }
240: if (resultGeometry != null)
241: return;
242: }
243:
244: // tried everything - have to bail
245: throw saveException;
246: }
247:
248: private void bufferOriginalPrecision() {
249: try {
250: // use fast noding by default
251: BufferBuilder bufBuilder = new BufferBuilder();
252: bufBuilder.setQuadrantSegments(quadrantSegments);
253: bufBuilder.setEndCapStyle(endCapStyle);
254: resultGeometry = bufBuilder.buffer(argGeom, distance);
255: } catch (RuntimeException ex) {
256: saveException = ex;
257: // don't propagate the exception - it will be detected by fact that resultGeometry is null
258:
259: // testing - propagate exception
260: //throw ex;
261: }
262: }
263:
264: private void bufferReducedPrecision(int precisionDigits) {
265: double sizeBasedScaleFactor = precisionScaleFactor(argGeom,
266: distance, precisionDigits);
267: // System.out.println("recomputing with precision scale factor = " + sizeBasedScaleFactor);
268:
269: PrecisionModel fixedPM = new PrecisionModel(
270: sizeBasedScaleFactor);
271: bufferFixedPrecision(fixedPM);
272: }
273:
274: private void bufferFixedPrecision(PrecisionModel fixedPM) {
275: Noder noder = new ScaledNoder(new MCIndexSnapRounder(
276: new PrecisionModel(1.0)), fixedPM.getScale());
277:
278: BufferBuilder bufBuilder = new BufferBuilder();
279: bufBuilder.setWorkingPrecisionModel(fixedPM);
280: bufBuilder.setNoder(noder);
281: bufBuilder.setQuadrantSegments(quadrantSegments);
282: bufBuilder.setEndCapStyle(endCapStyle);
283: // this may throw an exception, if robustness errors are encountered
284: resultGeometry = bufBuilder.buffer(argGeom, distance);
285: }
286: }
|