index.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import { feature } from "@turf/helpers";
  2. import { getCoords, getType } from "@turf/invariant";
  3. // To-Do => Improve Typescript GeoJSON handling
  4. /**
  5. * Removes redundant coordinates from any GeoJSON Geometry.
  6. *
  7. * @name cleanCoords
  8. * @param {Geometry|Feature} geojson Feature or Geometry
  9. * @param {Object} [options={}] Optional parameters
  10. * @param {boolean} [options.mutate=false] allows GeoJSON input to be mutated
  11. * @returns {Geometry|Feature} the cleaned input Feature/Geometry
  12. * @example
  13. * var line = turf.lineString([[0, 0], [0, 2], [0, 5], [0, 8], [0, 8], [0, 10]]);
  14. * var multiPoint = turf.multiPoint([[0, 0], [0, 0], [2, 2]]);
  15. *
  16. * turf.cleanCoords(line).geometry.coordinates;
  17. * //= [[0, 0], [0, 10]]
  18. *
  19. * turf.cleanCoords(multiPoint).geometry.coordinates;
  20. * //= [[0, 0], [2, 2]]
  21. */
  22. function cleanCoords(geojson, options) {
  23. if (options === void 0) { options = {}; }
  24. // Backwards compatible with v4.0
  25. var mutate = typeof options === "object" ? options.mutate : options;
  26. if (!geojson)
  27. throw new Error("geojson is required");
  28. var type = getType(geojson);
  29. // Store new "clean" points in this Array
  30. var newCoords = [];
  31. switch (type) {
  32. case "LineString":
  33. newCoords = cleanLine(geojson);
  34. break;
  35. case "MultiLineString":
  36. case "Polygon":
  37. getCoords(geojson).forEach(function (line) {
  38. newCoords.push(cleanLine(line));
  39. });
  40. break;
  41. case "MultiPolygon":
  42. getCoords(geojson).forEach(function (polygons) {
  43. var polyPoints = [];
  44. polygons.forEach(function (ring) {
  45. polyPoints.push(cleanLine(ring));
  46. });
  47. newCoords.push(polyPoints);
  48. });
  49. break;
  50. case "Point":
  51. return geojson;
  52. case "MultiPoint":
  53. var existing = {};
  54. getCoords(geojson).forEach(function (coord) {
  55. var key = coord.join("-");
  56. if (!Object.prototype.hasOwnProperty.call(existing, key)) {
  57. newCoords.push(coord);
  58. existing[key] = true;
  59. }
  60. });
  61. break;
  62. default:
  63. throw new Error(type + " geometry not supported");
  64. }
  65. // Support input mutation
  66. if (geojson.coordinates) {
  67. if (mutate === true) {
  68. geojson.coordinates = newCoords;
  69. return geojson;
  70. }
  71. return { type: type, coordinates: newCoords };
  72. }
  73. else {
  74. if (mutate === true) {
  75. geojson.geometry.coordinates = newCoords;
  76. return geojson;
  77. }
  78. return feature({ type: type, coordinates: newCoords }, geojson.properties, {
  79. bbox: geojson.bbox,
  80. id: geojson.id,
  81. });
  82. }
  83. }
  84. /**
  85. * Clean Coords
  86. *
  87. * @private
  88. * @param {Array<number>|LineString} line Line
  89. * @returns {Array<number>} Cleaned coordinates
  90. */
  91. function cleanLine(line) {
  92. var points = getCoords(line);
  93. // handle "clean" segment
  94. if (points.length === 2 && !equals(points[0], points[1]))
  95. return points;
  96. var newPoints = [];
  97. var secondToLast = points.length - 1;
  98. var newPointsLength = newPoints.length;
  99. newPoints.push(points[0]);
  100. for (var i = 1; i < secondToLast; i++) {
  101. var prevAddedPoint = newPoints[newPoints.length - 1];
  102. if (points[i][0] === prevAddedPoint[0] &&
  103. points[i][1] === prevAddedPoint[1])
  104. continue;
  105. else {
  106. newPoints.push(points[i]);
  107. newPointsLength = newPoints.length;
  108. if (newPointsLength > 2) {
  109. if (isPointOnLineSegment(newPoints[newPointsLength - 3], newPoints[newPointsLength - 1], newPoints[newPointsLength - 2]))
  110. newPoints.splice(newPoints.length - 2, 1);
  111. }
  112. }
  113. }
  114. newPoints.push(points[points.length - 1]);
  115. newPointsLength = newPoints.length;
  116. if (equals(points[0], points[points.length - 1]) && newPointsLength < 4)
  117. throw new Error("invalid polygon");
  118. if (isPointOnLineSegment(newPoints[newPointsLength - 3], newPoints[newPointsLength - 1], newPoints[newPointsLength - 2]))
  119. newPoints.splice(newPoints.length - 2, 1);
  120. return newPoints;
  121. }
  122. /**
  123. * Compares two points and returns if they are equals
  124. *
  125. * @private
  126. * @param {Position} pt1 point
  127. * @param {Position} pt2 point
  128. * @returns {boolean} true if they are equals
  129. */
  130. function equals(pt1, pt2) {
  131. return pt1[0] === pt2[0] && pt1[1] === pt2[1];
  132. }
  133. /**
  134. * Returns if `point` is on the segment between `start` and `end`.
  135. * Borrowed from `@turf/boolean-point-on-line` to speed up the evaluation (instead of using the module as dependency)
  136. *
  137. * @private
  138. * @param {Position} start coord pair of start of line
  139. * @param {Position} end coord pair of end of line
  140. * @param {Position} point coord pair of point to check
  141. * @returns {boolean} true/false
  142. */
  143. function isPointOnLineSegment(start, end, point) {
  144. var x = point[0], y = point[1];
  145. var startX = start[0], startY = start[1];
  146. var endX = end[0], endY = end[1];
  147. var dxc = x - startX;
  148. var dyc = y - startY;
  149. var dxl = endX - startX;
  150. var dyl = endY - startY;
  151. var cross = dxc * dyl - dyc * dxl;
  152. if (cross !== 0)
  153. return false;
  154. else if (Math.abs(dxl) >= Math.abs(dyl))
  155. return dxl > 0 ? startX <= x && x <= endX : endX <= x && x <= startX;
  156. else
  157. return dyl > 0 ? startY <= y && y <= endY : endY <= y && y <= startY;
  158. }
  159. export default cleanCoords;