index.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. 'use strict';
  2. var cleanCoords = require('@turf/clean-coords');
  3. var clone = require('@turf/clone');
  4. var meta = require('@turf/meta');
  5. var helpers = require('@turf/helpers');
  6. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  7. var cleanCoords__default = /*#__PURE__*/_interopDefaultLegacy(cleanCoords);
  8. var clone__default = /*#__PURE__*/_interopDefaultLegacy(clone);
  9. /*
  10. (c) 2013, Vladimir Agafonkin
  11. Simplify.js, a high-performance JS polyline simplification library
  12. mourner.github.io/simplify-js
  13. */
  14. // to suit your point format, run search/replace for '.x' and '.y';
  15. // for 3D version, see 3d branch (configurability would draw significant performance overhead)
  16. // square distance between 2 points
  17. function getSqDist(p1, p2) {
  18. var dx = p1.x - p2.x,
  19. dy = p1.y - p2.y;
  20. return dx * dx + dy * dy;
  21. }
  22. // square distance from a point to a segment
  23. function getSqSegDist(p, p1, p2) {
  24. var x = p1.x,
  25. y = p1.y,
  26. dx = p2.x - x,
  27. dy = p2.y - y;
  28. if (dx !== 0 || dy !== 0) {
  29. var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
  30. if (t > 1) {
  31. x = p2.x;
  32. y = p2.y;
  33. } else if (t > 0) {
  34. x += dx * t;
  35. y += dy * t;
  36. }
  37. }
  38. dx = p.x - x;
  39. dy = p.y - y;
  40. return dx * dx + dy * dy;
  41. }
  42. // rest of the code doesn't care about point format
  43. // basic distance-based simplification
  44. function simplifyRadialDist(points, sqTolerance) {
  45. var prevPoint = points[0],
  46. newPoints = [prevPoint],
  47. point;
  48. for (var i = 1, len = points.length; i < len; i++) {
  49. point = points[i];
  50. if (getSqDist(point, prevPoint) > sqTolerance) {
  51. newPoints.push(point);
  52. prevPoint = point;
  53. }
  54. }
  55. if (prevPoint !== point) newPoints.push(point);
  56. return newPoints;
  57. }
  58. function simplifyDPStep(points, first, last, sqTolerance, simplified) {
  59. var maxSqDist = sqTolerance,
  60. index;
  61. for (var i = first + 1; i < last; i++) {
  62. var sqDist = getSqSegDist(points[i], points[first], points[last]);
  63. if (sqDist > maxSqDist) {
  64. index = i;
  65. maxSqDist = sqDist;
  66. }
  67. }
  68. if (maxSqDist > sqTolerance) {
  69. if (index - first > 1)
  70. simplifyDPStep(points, first, index, sqTolerance, simplified);
  71. simplified.push(points[index]);
  72. if (last - index > 1)
  73. simplifyDPStep(points, index, last, sqTolerance, simplified);
  74. }
  75. }
  76. // simplification using Ramer-Douglas-Peucker algorithm
  77. function simplifyDouglasPeucker(points, sqTolerance) {
  78. var last = points.length - 1;
  79. var simplified = [points[0]];
  80. simplifyDPStep(points, 0, last, sqTolerance, simplified);
  81. simplified.push(points[last]);
  82. return simplified;
  83. }
  84. // both algorithms combined for awesome performance
  85. function simplify(points, tolerance, highestQuality) {
  86. if (points.length <= 2) return points;
  87. var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
  88. points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
  89. points = simplifyDouglasPeucker(points, sqTolerance);
  90. return points;
  91. }
  92. /**
  93. * Takes a {@link GeoJSON} object and returns a simplified version. Internally uses
  94. * [simplify-js](http://mourner.github.io/simplify-js/) to perform simplification using the Ramer-Douglas-Peucker algorithm.
  95. *
  96. * @name simplify
  97. * @param {GeoJSON} geojson object to be simplified
  98. * @param {Object} [options={}] Optional parameters
  99. * @param {number} [options.tolerance=1] simplification tolerance
  100. * @param {boolean} [options.highQuality=false] whether or not to spend more time to create a higher-quality simplification with a different algorithm
  101. * @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated (significant performance increase if true)
  102. * @returns {GeoJSON} a simplified GeoJSON
  103. * @example
  104. * var geojson = turf.polygon([[
  105. * [-70.603637, -33.399918],
  106. * [-70.614624, -33.395332],
  107. * [-70.639343, -33.392466],
  108. * [-70.659942, -33.394759],
  109. * [-70.683975, -33.404504],
  110. * [-70.697021, -33.419406],
  111. * [-70.701141, -33.434306],
  112. * [-70.700454, -33.446339],
  113. * [-70.694274, -33.458369],
  114. * [-70.682601, -33.465816],
  115. * [-70.668869, -33.472117],
  116. * [-70.646209, -33.473835],
  117. * [-70.624923, -33.472117],
  118. * [-70.609817, -33.468107],
  119. * [-70.595397, -33.458369],
  120. * [-70.587158, -33.442901],
  121. * [-70.587158, -33.426283],
  122. * [-70.590591, -33.414248],
  123. * [-70.594711, -33.406224],
  124. * [-70.603637, -33.399918]
  125. * ]]);
  126. * var options = {tolerance: 0.01, highQuality: false};
  127. * var simplified = turf.simplify(geojson, options);
  128. *
  129. * //addToMap
  130. * var addToMap = [geojson, simplified]
  131. */
  132. function simplify$1(geojson, options) {
  133. // Optional parameters
  134. options = options || {};
  135. if (!helpers.isObject(options)) throw new Error("options is invalid");
  136. var tolerance = options.tolerance !== undefined ? options.tolerance : 1;
  137. var highQuality = options.highQuality || false;
  138. var mutate = options.mutate || false;
  139. if (!geojson) throw new Error("geojson is required");
  140. if (tolerance && tolerance < 0) throw new Error("invalid tolerance");
  141. // Clone geojson to avoid side effects
  142. if (mutate !== true) geojson = clone__default['default'](geojson);
  143. meta.geomEach(geojson, function (geom) {
  144. simplifyGeom(geom, tolerance, highQuality);
  145. });
  146. return geojson;
  147. }
  148. /**
  149. * Simplifies a feature's coordinates
  150. *
  151. * @private
  152. * @param {Geometry} geometry to be simplified
  153. * @param {number} [tolerance=1] simplification tolerance
  154. * @param {boolean} [highQuality=false] whether or not to spend more time to create a higher-quality simplification with a different algorithm
  155. * @returns {Geometry} output
  156. */
  157. function simplifyGeom(geometry, tolerance, highQuality) {
  158. var type = geometry.type;
  159. // "unsimplyfiable" geometry types
  160. if (type === "Point" || type === "MultiPoint") return geometry;
  161. // Remove any extra coordinates
  162. cleanCoords__default['default'](geometry, true);
  163. var coordinates = geometry.coordinates;
  164. switch (type) {
  165. case "LineString":
  166. geometry["coordinates"] = simplifyLine(
  167. coordinates,
  168. tolerance,
  169. highQuality
  170. );
  171. break;
  172. case "MultiLineString":
  173. geometry["coordinates"] = coordinates.map(function (lines) {
  174. return simplifyLine(lines, tolerance, highQuality);
  175. });
  176. break;
  177. case "Polygon":
  178. geometry["coordinates"] = simplifyPolygon(
  179. coordinates,
  180. tolerance,
  181. highQuality
  182. );
  183. break;
  184. case "MultiPolygon":
  185. geometry["coordinates"] = coordinates.map(function (rings) {
  186. return simplifyPolygon(rings, tolerance, highQuality);
  187. });
  188. }
  189. return geometry;
  190. }
  191. /**
  192. * Simplifies the coordinates of a LineString with simplify-js
  193. *
  194. * @private
  195. * @param {Array<number>} coordinates to be processed
  196. * @param {number} tolerance simplification tolerance
  197. * @param {boolean} highQuality whether or not to spend more time to create a higher-quality
  198. * @returns {Array<Array<number>>} simplified coords
  199. */
  200. function simplifyLine(coordinates, tolerance, highQuality) {
  201. return simplify(
  202. coordinates.map(function (coord) {
  203. return { x: coord[0], y: coord[1], z: coord[2] };
  204. }),
  205. tolerance,
  206. highQuality
  207. ).map(function (coords) {
  208. return coords.z ? [coords.x, coords.y, coords.z] : [coords.x, coords.y];
  209. });
  210. }
  211. /**
  212. * Simplifies the coordinates of a Polygon with simplify-js
  213. *
  214. * @private
  215. * @param {Array<number>} coordinates to be processed
  216. * @param {number} tolerance simplification tolerance
  217. * @param {boolean} highQuality whether or not to spend more time to create a higher-quality
  218. * @returns {Array<Array<Array<number>>>} simplified coords
  219. */
  220. function simplifyPolygon(coordinates, tolerance, highQuality) {
  221. return coordinates.map(function (ring) {
  222. var pts = ring.map(function (coord) {
  223. return { x: coord[0], y: coord[1] };
  224. });
  225. if (pts.length < 4) {
  226. throw new Error("invalid polygon");
  227. }
  228. var simpleRing = simplify(pts, tolerance, highQuality).map(function (
  229. coords
  230. ) {
  231. return [coords.x, coords.y];
  232. });
  233. //remove 1 percent of tolerance until enough points to make a triangle
  234. while (!checkValidity(simpleRing)) {
  235. tolerance -= tolerance * 0.01;
  236. simpleRing = simplify(pts, tolerance, highQuality).map(function (
  237. coords
  238. ) {
  239. return [coords.x, coords.y];
  240. });
  241. }
  242. if (
  243. simpleRing[simpleRing.length - 1][0] !== simpleRing[0][0] ||
  244. simpleRing[simpleRing.length - 1][1] !== simpleRing[0][1]
  245. ) {
  246. simpleRing.push(simpleRing[0]);
  247. }
  248. return simpleRing;
  249. });
  250. }
  251. /**
  252. * Returns true if ring has at least 3 coordinates and its first coordinate is the same as its last
  253. *
  254. * @private
  255. * @param {Array<number>} ring coordinates to be checked
  256. * @returns {boolean} true if valid
  257. */
  258. function checkValidity(ring) {
  259. if (ring.length < 3) return false;
  260. //if the last point is the same as the first, it's not a triangle
  261. return !(
  262. ring.length === 3 &&
  263. ring[2][0] === ring[0][0] &&
  264. ring[2][1] === ring[0][1]
  265. );
  266. }
  267. module.exports = simplify$1;
  268. module.exports.default = simplify$1;