309 lines
8.9 KiB
JavaScript
Executable File
309 lines
8.9 KiB
JavaScript
Executable File
'use strict';
|
|
|
|
var cleanCoords = require('@turf/clean-coords');
|
|
var clone = require('@turf/clone');
|
|
var meta = require('@turf/meta');
|
|
var helpers = require('@turf/helpers');
|
|
|
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
|
|
var cleanCoords__default = /*#__PURE__*/_interopDefaultLegacy(cleanCoords);
|
|
var clone__default = /*#__PURE__*/_interopDefaultLegacy(clone);
|
|
|
|
/*
|
|
(c) 2013, Vladimir Agafonkin
|
|
Simplify.js, a high-performance JS polyline simplification library
|
|
mourner.github.io/simplify-js
|
|
*/
|
|
|
|
// to suit your point format, run search/replace for '.x' and '.y';
|
|
// for 3D version, see 3d branch (configurability would draw significant performance overhead)
|
|
|
|
// square distance between 2 points
|
|
function getSqDist(p1, p2) {
|
|
var dx = p1.x - p2.x,
|
|
dy = p1.y - p2.y;
|
|
|
|
return dx * dx + dy * dy;
|
|
}
|
|
|
|
// square distance from a point to a segment
|
|
function getSqSegDist(p, p1, p2) {
|
|
var x = p1.x,
|
|
y = p1.y,
|
|
dx = p2.x - x,
|
|
dy = p2.y - y;
|
|
|
|
if (dx !== 0 || dy !== 0) {
|
|
var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
|
|
|
|
if (t > 1) {
|
|
x = p2.x;
|
|
y = p2.y;
|
|
} else if (t > 0) {
|
|
x += dx * t;
|
|
y += dy * t;
|
|
}
|
|
}
|
|
|
|
dx = p.x - x;
|
|
dy = p.y - y;
|
|
|
|
return dx * dx + dy * dy;
|
|
}
|
|
// rest of the code doesn't care about point format
|
|
|
|
// basic distance-based simplification
|
|
function simplifyRadialDist(points, sqTolerance) {
|
|
var prevPoint = points[0],
|
|
newPoints = [prevPoint],
|
|
point;
|
|
|
|
for (var i = 1, len = points.length; i < len; i++) {
|
|
point = points[i];
|
|
|
|
if (getSqDist(point, prevPoint) > sqTolerance) {
|
|
newPoints.push(point);
|
|
prevPoint = point;
|
|
}
|
|
}
|
|
|
|
if (prevPoint !== point) newPoints.push(point);
|
|
|
|
return newPoints;
|
|
}
|
|
|
|
function simplifyDPStep(points, first, last, sqTolerance, simplified) {
|
|
var maxSqDist = sqTolerance,
|
|
index;
|
|
|
|
for (var i = first + 1; i < last; i++) {
|
|
var sqDist = getSqSegDist(points[i], points[first], points[last]);
|
|
|
|
if (sqDist > maxSqDist) {
|
|
index = i;
|
|
maxSqDist = sqDist;
|
|
}
|
|
}
|
|
|
|
if (maxSqDist > sqTolerance) {
|
|
if (index - first > 1)
|
|
simplifyDPStep(points, first, index, sqTolerance, simplified);
|
|
simplified.push(points[index]);
|
|
if (last - index > 1)
|
|
simplifyDPStep(points, index, last, sqTolerance, simplified);
|
|
}
|
|
}
|
|
|
|
// simplification using Ramer-Douglas-Peucker algorithm
|
|
function simplifyDouglasPeucker(points, sqTolerance) {
|
|
var last = points.length - 1;
|
|
|
|
var simplified = [points[0]];
|
|
simplifyDPStep(points, 0, last, sqTolerance, simplified);
|
|
simplified.push(points[last]);
|
|
|
|
return simplified;
|
|
}
|
|
|
|
// both algorithms combined for awesome performance
|
|
function simplify(points, tolerance, highestQuality) {
|
|
if (points.length <= 2) return points;
|
|
|
|
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
|
|
|
|
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
|
|
points = simplifyDouglasPeucker(points, sqTolerance);
|
|
|
|
return points;
|
|
}
|
|
|
|
/**
|
|
* Takes a {@link GeoJSON} object and returns a simplified version. Internally uses
|
|
* [simplify-js](http://mourner.github.io/simplify-js/) to perform simplification using the Ramer-Douglas-Peucker algorithm.
|
|
*
|
|
* @name simplify
|
|
* @param {GeoJSON} geojson object to be simplified
|
|
* @param {Object} [options={}] Optional parameters
|
|
* @param {number} [options.tolerance=1] simplification tolerance
|
|
* @param {boolean} [options.highQuality=false] whether or not to spend more time to create a higher-quality simplification with a different algorithm
|
|
* @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
|
|
* @returns {GeoJSON} a simplified GeoJSON
|
|
* @example
|
|
* var geojson = turf.polygon([[
|
|
* [-70.603637, -33.399918],
|
|
* [-70.614624, -33.395332],
|
|
* [-70.639343, -33.392466],
|
|
* [-70.659942, -33.394759],
|
|
* [-70.683975, -33.404504],
|
|
* [-70.697021, -33.419406],
|
|
* [-70.701141, -33.434306],
|
|
* [-70.700454, -33.446339],
|
|
* [-70.694274, -33.458369],
|
|
* [-70.682601, -33.465816],
|
|
* [-70.668869, -33.472117],
|
|
* [-70.646209, -33.473835],
|
|
* [-70.624923, -33.472117],
|
|
* [-70.609817, -33.468107],
|
|
* [-70.595397, -33.458369],
|
|
* [-70.587158, -33.442901],
|
|
* [-70.587158, -33.426283],
|
|
* [-70.590591, -33.414248],
|
|
* [-70.594711, -33.406224],
|
|
* [-70.603637, -33.399918]
|
|
* ]]);
|
|
* var options = {tolerance: 0.01, highQuality: false};
|
|
* var simplified = turf.simplify(geojson, options);
|
|
*
|
|
* //addToMap
|
|
* var addToMap = [geojson, simplified]
|
|
*/
|
|
function simplify$1(geojson, options) {
|
|
// Optional parameters
|
|
options = options || {};
|
|
if (!helpers.isObject(options)) throw new Error("options is invalid");
|
|
var tolerance = options.tolerance !== undefined ? options.tolerance : 1;
|
|
var highQuality = options.highQuality || false;
|
|
var mutate = options.mutate || false;
|
|
|
|
if (!geojson) throw new Error("geojson is required");
|
|
if (tolerance && tolerance < 0) throw new Error("invalid tolerance");
|
|
|
|
// Clone geojson to avoid side effects
|
|
if (mutate !== true) geojson = clone__default['default'](geojson);
|
|
|
|
meta.geomEach(geojson, function (geom) {
|
|
simplifyGeom(geom, tolerance, highQuality);
|
|
});
|
|
return geojson;
|
|
}
|
|
|
|
/**
|
|
* Simplifies a feature's coordinates
|
|
*
|
|
* @private
|
|
* @param {Geometry} geometry to be simplified
|
|
* @param {number} [tolerance=1] simplification tolerance
|
|
* @param {boolean} [highQuality=false] whether or not to spend more time to create a higher-quality simplification with a different algorithm
|
|
* @returns {Geometry} output
|
|
*/
|
|
function simplifyGeom(geometry, tolerance, highQuality) {
|
|
var type = geometry.type;
|
|
|
|
// "unsimplyfiable" geometry types
|
|
if (type === "Point" || type === "MultiPoint") return geometry;
|
|
|
|
// Remove any extra coordinates
|
|
cleanCoords__default['default'](geometry, true);
|
|
|
|
var coordinates = geometry.coordinates;
|
|
switch (type) {
|
|
case "LineString":
|
|
geometry["coordinates"] = simplifyLine(
|
|
coordinates,
|
|
tolerance,
|
|
highQuality
|
|
);
|
|
break;
|
|
case "MultiLineString":
|
|
geometry["coordinates"] = coordinates.map(function (lines) {
|
|
return simplifyLine(lines, tolerance, highQuality);
|
|
});
|
|
break;
|
|
case "Polygon":
|
|
geometry["coordinates"] = simplifyPolygon(
|
|
coordinates,
|
|
tolerance,
|
|
highQuality
|
|
);
|
|
break;
|
|
case "MultiPolygon":
|
|
geometry["coordinates"] = coordinates.map(function (rings) {
|
|
return simplifyPolygon(rings, tolerance, highQuality);
|
|
});
|
|
}
|
|
return geometry;
|
|
}
|
|
|
|
/**
|
|
* Simplifies the coordinates of a LineString with simplify-js
|
|
*
|
|
* @private
|
|
* @param {Array<number>} coordinates to be processed
|
|
* @param {number} tolerance simplification tolerance
|
|
* @param {boolean} highQuality whether or not to spend more time to create a higher-quality
|
|
* @returns {Array<Array<number>>} simplified coords
|
|
*/
|
|
function simplifyLine(coordinates, tolerance, highQuality) {
|
|
return simplify(
|
|
coordinates.map(function (coord) {
|
|
return { x: coord[0], y: coord[1], z: coord[2] };
|
|
}),
|
|
tolerance,
|
|
highQuality
|
|
).map(function (coords) {
|
|
return coords.z ? [coords.x, coords.y, coords.z] : [coords.x, coords.y];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Simplifies the coordinates of a Polygon with simplify-js
|
|
*
|
|
* @private
|
|
* @param {Array<number>} coordinates to be processed
|
|
* @param {number} tolerance simplification tolerance
|
|
* @param {boolean} highQuality whether or not to spend more time to create a higher-quality
|
|
* @returns {Array<Array<Array<number>>>} simplified coords
|
|
*/
|
|
function simplifyPolygon(coordinates, tolerance, highQuality) {
|
|
return coordinates.map(function (ring) {
|
|
var pts = ring.map(function (coord) {
|
|
return { x: coord[0], y: coord[1] };
|
|
});
|
|
if (pts.length < 4) {
|
|
throw new Error("invalid polygon");
|
|
}
|
|
var simpleRing = simplify(pts, tolerance, highQuality).map(function (
|
|
coords
|
|
) {
|
|
return [coords.x, coords.y];
|
|
});
|
|
//remove 1 percent of tolerance until enough points to make a triangle
|
|
while (!checkValidity(simpleRing)) {
|
|
tolerance -= tolerance * 0.01;
|
|
simpleRing = simplify(pts, tolerance, highQuality).map(function (
|
|
coords
|
|
) {
|
|
return [coords.x, coords.y];
|
|
});
|
|
}
|
|
if (
|
|
simpleRing[simpleRing.length - 1][0] !== simpleRing[0][0] ||
|
|
simpleRing[simpleRing.length - 1][1] !== simpleRing[0][1]
|
|
) {
|
|
simpleRing.push(simpleRing[0]);
|
|
}
|
|
return simpleRing;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns true if ring has at least 3 coordinates and its first coordinate is the same as its last
|
|
*
|
|
* @private
|
|
* @param {Array<number>} ring coordinates to be checked
|
|
* @returns {boolean} true if valid
|
|
*/
|
|
function checkValidity(ring) {
|
|
if (ring.length < 3) return false;
|
|
//if the last point is the same as the first, it's not a triangle
|
|
return !(
|
|
ring.length === 3 &&
|
|
ring[2][0] === ring[0][0] &&
|
|
ring[2][1] === ring[0][1]
|
|
);
|
|
}
|
|
|
|
module.exports = simplify$1;
|
|
module.exports.default = simplify$1;
|