/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.operation.buffer; import com.revolsys.geometry.algorithm.CGAlgorithms; import com.revolsys.geometry.algorithm.CGAlgorithmsDD; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.coordinates.LineSegmentUtil; import com.revolsys.geometry.model.coordinates.list.CoordinatesListUtil; import com.revolsys.geometry.model.impl.LineStringDouble; /** * Simplifies a buffer input line to * remove concavities with shallow depth. * <p> * The most important benefit of doing this * is to reduce the number of points and the complexity of * shape which will be buffered. * It also reduces the risk of gores created by * the quantized fillet arcs (although this issue * should be eliminated in any case by the * offset curve generation logic). * <p> * A key aspect of the simplification is that it * affects inside (concave or inward) corners only. * Convex (outward) corners are preserved, since they * are required to ensure that the generated buffer curve * lies at the correct distance from the input geometry. * <p> * Another important heuristic used is that the end segments * of the input are never simplified. This ensures that * the client buffer code is able to generate end caps faithfully. * <p> * No attempt is made to avoid self-intersections in the output. * This is acceptable for use for generating a buffer offset curve, * since the buffer algorithm is insensitive to invalid polygonal * geometry. However, * this means that this algorithm * cannot be used as a general-purpose polygon simplification technique. * * @author Martin Davis * */ public class BufferInputLineSimplifier { private static final int DELETE = 1; private static final int NUM_PTS_TO_CHECK = 10; /** * Simplify the input coordinate list. * If the distance tolerance is positive, * concavities on the LEFT side of the line are simplified. * If the supplied distance tolerance is negative, * concavities on the RIGHT side of the line are simplified. * * @param inputLine the coordinate list to simplify * @param distanceTol simplification distance tolerance to use * @return the simplified coordinate list */ public static LineString simplify(final LineString inputLine, final double distanceTol) { final BufferInputLineSimplifier simp = new BufferInputLineSimplifier(inputLine); return simp.simplify(distanceTol); } private int angleOrientation = CGAlgorithms.COUNTERCLOCKWISE; private int deleteCount = 0; private double distanceTol; private final LineString inputLine; private byte[] isDeleted; public BufferInputLineSimplifier(final LineString inputLine) { this.inputLine = inputLine; } private LineString collapseLine() { final int axisCount = this.inputLine.getAxisCount(); final int vertexCount = this.inputLine.getVertexCount(); final double[] coordinates = new double[(vertexCount - this.deleteCount) * axisCount]; int j = 0; for (int i = 0; i < vertexCount; i++) { if (this.isDeleted[i] != DELETE) { CoordinatesListUtil.setCoordinates(coordinates, axisCount, j++, this.inputLine, i); } } return new LineStringDouble(axisCount, coordinates); } /** * Uses a sliding window containing 3 vertices to detect shallow angles * in which the middle vertex can be deleted, since it does not * affect the shape of the resulting buffer in a significant way. * @return */ private boolean deleteShallowConcavities() { /** * Do not simplify end line segments of the line string. * This ensures that end caps are generated consistently. */ int index = 1; int midIndex = findNextNonDeletedIndex(index); int lastIndex = findNextNonDeletedIndex(midIndex); boolean isChanged = false; while (lastIndex < this.inputLine.getVertexCount()) { // test triple for shallow concavity boolean isMiddleVertexDeleted = false; if (isDeletable(index, midIndex, lastIndex, this.distanceTol)) { this.deleteCount++; this.isDeleted[midIndex] = DELETE; isMiddleVertexDeleted = true; isChanged = true; } // move simplification window forward if (isMiddleVertexDeleted) { index = lastIndex; } else { index = midIndex; } midIndex = findNextNonDeletedIndex(index); lastIndex = findNextNonDeletedIndex(midIndex); } return isChanged; } /** * Finds the next non-deleted index, or the end of the point array if none * @param index * @return the next non-deleted index, if any * or inputLine.length if there are no more non-deleted indices */ private int findNextNonDeletedIndex(final int index) { int next = index + 1; while (next < this.inputLine.getVertexCount() && this.isDeleted[next] == DELETE) { next++; } return next; } private boolean isConcave(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3) { final int orientation = CGAlgorithmsDD.orientationIndex(x1, y1, x2, y2, x3, y3); final boolean isConcave = orientation == this.angleOrientation; return isConcave; } private boolean isDeletable(final int i0, final int i1, final int i2, final double distanceTol) { final double x1 = this.inputLine.getX(i0); final double y1 = this.inputLine.getY(i0); final double x2 = this.inputLine.getX(i1); final double y2 = this.inputLine.getY(i1); final double x3 = this.inputLine.getX(i2); final double y3 = this.inputLine.getY(i2); if (!isConcave(x1, y1, x2, y2, x3, y3)) { return false; } if (!isShallow(x1, y1, x2, y2, x3, y3, distanceTol)) { return false; } // MD - don't use this heuristic - it's too restricting // if (p0.distance(p2) > distanceTol) return false; return isShallowSampled(x1, y1, x2, y2, i0, i2, distanceTol); } private boolean isShallow(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3, final double distanceTol) { final double dist = LineSegmentUtil.distanceLinePoint(x1, y1, x3, y3, x2, y2); return dist < distanceTol; } /** * Checks for shallowness over a sample of points in the given section. * This helps prevents the siplification from incrementally * "skipping" over points which are in fact non-shallow. * * @param p0 start coordinate of section * @param p2 end coordinate of section * @param i0 start index of section * @param i2 end index of section * @param distanceTol distance tolerance * @return */ private boolean isShallowSampled(final double x1, final double y1, final double x2, final double y2, final int i0, final int i2, final double distanceTol) { // check every n'th point to see if it is within tolerance int inc = (i2 - i0) / NUM_PTS_TO_CHECK; if (inc <= 0) { inc = 1; } for (int i = i0; i < i2; i += inc) { final double x3 = this.inputLine.getX(i); final double y3 = this.inputLine.getY(i); if (!isShallow(x1, y1, x2, y2, x3, y3, distanceTol)) { return false; } } return true; } /** * Simplify the input coordinate list. * If the distance tolerance is positive, * concavities on the LEFT side of the line are simplified. * If the supplied distance tolerance is negative, * concavities on the RIGHT side of the line are simplified. * * @param distanceTol simplification distance tolerance to use * @return the simplified coordinate list */ public LineString simplify(final double distanceTol) { this.distanceTol = Math.abs(distanceTol); if (distanceTol < 0) { this.angleOrientation = CGAlgorithms.CLOCKWISE; } // rely on fact that boolean array is filled with false value this.isDeleted = new byte[this.inputLine.getVertexCount()]; boolean isChanged = false; do { isChanged = deleteShallowConcavities(); } while (isChanged); return collapseLine(); } }