index.js 8.6 KB

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