/* * Copyright (c) 2016 Vivid Solutions. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v. 1.0 which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * * http://www.eclipse.org/org/documents/edl-v10.php. */ package org.locationtech.jts.operation.buffer; import java.util.ArrayList; import java.util.List; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineSegment; import org.locationtech.jts.geom.LineString; /** * Creates a buffer polygon with variable width along a line. * <p> * Only single lines are supported as input, since buffer widths * generally need to be specified specifically for each line. * * @author Martin Davis * */ public class VariableWidthBuffer { /** * Creates a buffer polygon along a line with the width interpolated * between a start width and an end width. * * @param line the line to buffer * @param startWidth the buffer width at the start of the line * @param endWidth the buffer width at the end of the line * @return the variable-width buffer polygon */ public static Geometry buffer(Geometry line, double startWidth, double endWidth) { double[] width = VariableWidthBuffer.interpolate((LineString) line, startWidth, endWidth); VariableWidthBuffer vb = new VariableWidthBuffer(line, width); return vb.getResult(); } public static Geometry bufferAternating(Geometry line, double width1, double width2) { int nPts = line.getNumPoints(); double[] width = new double[nPts]; for (int i = 0; i < width.length; i++) { width[i] = (i % 2) == 0 ? width1 : width2; } VariableWidthBuffer vb = new VariableWidthBuffer(line, width); return vb.getResult(); } /** * Computes a list of values for the points along a line by * interpolating between values for the start and end point. * The interpolation is * based on the distance of each point along the line * relative to the total line length. * * @param line the line to interpolate along * @param start the start value * @param end the end value * @return the array of interpolated values */ public static double[] interpolate(LineString line, double start, double end) { start = Math.abs(start); end = Math.abs(end); double[] widths = new double[line.getNumPoints()]; widths[0] = start; widths[widths.length - 1] = end; double totalLen = line.getLength(); Coordinate[] pts = line.getCoordinates(); double currLen = 0; for (int i = 1; i < widths.length; i++) { double segLen = pts[i].distance(pts[i - 1]); currLen += segLen; double lenFrac = currLen / totalLen; double delta = lenFrac * (end - start); widths[i] = start + delta; } return widths; } private static double[] abs(double[] v) { double[] a = new double[v.length]; for (int i = 0; i < v.length; i++) { a[i] = Math.abs(v[i]); } return a; } private LineString line; private double[] width; private GeometryFactory geomFactory; /** * Creates a generator for a variable-width line buffer. * * @param line * @param width */ public VariableWidthBuffer(Geometry line, double[] width) { this.line = (LineString) line; this.width = abs(width); geomFactory = line.getFactory(); } /** * Gets the computed variable-width line buffer. * * @return a polygon */ public Geometry getResult() { List parts = new ArrayList(); Coordinate[] pts = line.getCoordinates(); for (int i = 0; i < line.getNumPoints(); i++) { double dist = width[i] / 2; // skip zero-width fillets if (dist > 0) { Geometry ptBuf = line.getPointN(i).buffer(dist); parts.add(ptBuf); } if (i == 0) continue; double width0 = width[i - 1]; double width1 = width[i]; if (width0 > 0 || width1 > 0) { Coordinate[] curvePts = generateSegmentCurve(pts[i - 1], pts[i], width0, width1); Geometry segBuf = geomFactory.createPolygon(curvePts); parts.add(segBuf); } } GeometryCollection partsGeom = geomFactory .createGeometryCollection(GeometryFactory.toGeometryArray(parts)); Geometry buffer = partsGeom.union(); return buffer; } private Coordinate[] generateSegmentCurve(Coordinate p0, Coordinate p1, double width0, double width1) { LineSegment seg = new LineSegment(p0, p1); double dist0 = width0 / 2; double dist1 = width1 / 2; Coordinate s0 = seg.pointAlongOffset(0, dist0); Coordinate s1 = seg.pointAlongOffset(1, dist1); Coordinate s2 = seg.pointAlongOffset(1, -dist1); Coordinate s3 = seg.pointAlongOffset(0, -dist0); Coordinate[] pts = new Coordinate[] { s0, s1, s2, s3, s0 }; return pts; } }