'use strict'; var meta = require('@turf/meta'); var helpers = require('@turf/helpers'); /** * Smooths a {@link Polygon} or {@link MultiPolygon}. Based on [Chaikin's algorithm](http://graphics.cs.ucdavis.edu/education/CAGDNotes/Chaikins-Algorithm/Chaikins-Algorithm.html). * Warning: may create degenerate polygons. * * @name polygonSmooth * @param {FeatureCollection|Feature} inputPolys (Multi)Polygon(s) to smooth * @param {Object} [options={}] Optional parameters * @param {string} [options.iterations=1] THe number of times to smooth the polygon. A higher value means a smoother polygon. * @returns {FeatureCollection} FeatureCollection containing the smoothed polygon/poylgons * @example * var polygon = turf.polygon([[[11, 0], [22, 4], [31, 0], [31, 11], [21, 15], [11, 11], [11, 0]]]); * * var smoothed = turf.polygonSmooth(polygon, {iterations: 3}) * * //addToMap * var addToMap = [smoothed, polygon]; */ function polygonSmooth(inputPolys, options) { var outPolys = []; // Optional parameters var iterations = options.iterations || 1; if (!inputPolys) throw new Error("inputPolys is required"); meta.geomEach(inputPolys, function (geom, geomIndex, properties) { var outCoords; var poly; var tempOutput; switch (geom.type) { case "Polygon": outCoords = [[]]; for (var i = 0; i < iterations; i++) { tempOutput = [[]]; poly = geom; if (i > 0) poly = helpers.polygon(outCoords).geometry; processPolygon(poly, tempOutput); outCoords = tempOutput.slice(0); } outPolys.push(helpers.polygon(outCoords, properties)); break; case "MultiPolygon": outCoords = [[[]]]; for (var y = 0; y < iterations; y++) { tempOutput = [[[]]]; poly = geom; if (y > 0) poly = helpers.multiPolygon(outCoords).geometry; processMultiPolygon(poly, tempOutput); outCoords = tempOutput.slice(0); } outPolys.push(helpers.multiPolygon(outCoords, properties)); break; default: throw new Error("geometry is invalid, must be Polygon or MultiPolygon"); } }); return helpers.featureCollection(outPolys); } /** * @param {poly} poly to process * @param {poly} tempOutput to place the results in * @private */ function processPolygon(poly, tempOutput) { var prevGeomIndex = 0; var subtractCoordIndex = 0; meta.coordEach( poly, function ( currentCoord, coordIndex, featureIndex, multiFeatureIndex, geometryIndex ) { if (geometryIndex > prevGeomIndex) { prevGeomIndex = geometryIndex; subtractCoordIndex = coordIndex; tempOutput.push([]); } var realCoordIndex = coordIndex - subtractCoordIndex; var p1 = poly.coordinates[geometryIndex][realCoordIndex + 1]; var p0x = currentCoord[0]; var p0y = currentCoord[1]; var p1x = p1[0]; var p1y = p1[1]; tempOutput[geometryIndex].push([ 0.75 * p0x + 0.25 * p1x, 0.75 * p0y + 0.25 * p1y, ]); tempOutput[geometryIndex].push([ 0.25 * p0x + 0.75 * p1x, 0.25 * p0y + 0.75 * p1y, ]); }, true ); tempOutput.forEach(function (ring) { ring.push(ring[0]); }); } /** * @param {poly} poly to process * @param {poly} tempOutput to place the results in * @private */ function processMultiPolygon(poly, tempOutput) { var prevGeomIndex = 0; var subtractCoordIndex = 0; var prevMultiIndex = 0; meta.coordEach( poly, function ( currentCoord, coordIndex, featureIndex, multiFeatureIndex, geometryIndex ) { if (multiFeatureIndex > prevMultiIndex) { prevMultiIndex = multiFeatureIndex; subtractCoordIndex = coordIndex; tempOutput.push([[]]); } if (geometryIndex > prevGeomIndex) { prevGeomIndex = geometryIndex; subtractCoordIndex = coordIndex; tempOutput[multiFeatureIndex].push([]); } var realCoordIndex = coordIndex - subtractCoordIndex; var p1 = poly.coordinates[multiFeatureIndex][geometryIndex][realCoordIndex + 1]; var p0x = currentCoord[0]; var p0y = currentCoord[1]; var p1x = p1[0]; var p1y = p1[1]; tempOutput[multiFeatureIndex][geometryIndex].push([ 0.75 * p0x + 0.25 * p1x, 0.75 * p0y + 0.25 * p1y, ]); tempOutput[multiFeatureIndex][geometryIndex].push([ 0.25 * p0x + 0.75 * p1x, 0.25 * p0y + 0.75 * p1y, ]); }, true ); tempOutput.forEach(function (poly) { poly.forEach(function (ring) { ring.push(ring[0]); }); }); } module.exports = polygonSmooth; module.exports.default = polygonSmooth;