index.js 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import distance from "@turf/distance";
  2. import { feature, featureCollection } from "@turf/helpers";
  3. import { featureEach } from "@turf/meta";
  4. import tin from "@turf/tin";
  5. import dissolve from "./lib/turf-dissolve.js";
  6. /**
  7. * Takes a set of {@link Point|points} and returns a concave hull Polygon or MultiPolygon.
  8. * Internally, this uses [turf-tin](https://github.com/Turfjs/turf-tin) to generate geometries.
  9. *
  10. * @name concave
  11. * @param {FeatureCollection<Point>} points input points
  12. * @param {Object} [options={}] Optional parameters
  13. * @param {number} [options.maxEdge=Infinity] the length (in 'units') of an edge necessary for part of the
  14. * hull to become concave.
  15. * @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
  16. * @returns {Feature<(Polygon|MultiPolygon)>|null} a concave hull (null value is returned if unable to compute hull)
  17. * @example
  18. * var points = turf.featureCollection([
  19. * turf.point([-63.601226, 44.642643]),
  20. * turf.point([-63.591442, 44.651436]),
  21. * turf.point([-63.580799, 44.648749]),
  22. * turf.point([-63.573589, 44.641788]),
  23. * turf.point([-63.587665, 44.64533]),
  24. * turf.point([-63.595218, 44.64765])
  25. * ]);
  26. * var options = {units: 'miles', maxEdge: 1};
  27. *
  28. * var hull = turf.concave(points, options);
  29. *
  30. * //addToMap
  31. * var addToMap = [points, hull]
  32. */
  33. function concave(points, options) {
  34. if (options === void 0) { options = {}; }
  35. var maxEdge = options.maxEdge || Infinity;
  36. var cleaned = removeDuplicates(points);
  37. var tinPolys = tin(cleaned);
  38. // calculate length of all edges and area of all triangles
  39. // and remove triangles that fail the max length test
  40. tinPolys.features = tinPolys.features.filter(function (triangle) {
  41. var pt1 = triangle.geometry.coordinates[0][0];
  42. var pt2 = triangle.geometry.coordinates[0][1];
  43. var pt3 = triangle.geometry.coordinates[0][2];
  44. var dist1 = distance(pt1, pt2, options);
  45. var dist2 = distance(pt2, pt3, options);
  46. var dist3 = distance(pt1, pt3, options);
  47. return dist1 <= maxEdge && dist2 <= maxEdge && dist3 <= maxEdge;
  48. });
  49. if (tinPolys.features.length < 1) {
  50. return null;
  51. }
  52. // merge the adjacent triangles
  53. var dissolved = dissolve(tinPolys);
  54. // geojson-dissolve always returns a MultiPolygon
  55. if (dissolved.coordinates.length === 1) {
  56. dissolved.coordinates = dissolved.coordinates[0];
  57. dissolved.type = "Polygon";
  58. }
  59. return feature(dissolved);
  60. }
  61. /**
  62. * Removes duplicated points in a collection returning a new collection
  63. *
  64. * @private
  65. * @param {FeatureCollection<Point>} points to be cleaned
  66. * @returns {FeatureCollection<Point>} cleaned set of points
  67. */
  68. function removeDuplicates(points) {
  69. var cleaned = [];
  70. var existing = {};
  71. featureEach(points, function (pt) {
  72. if (!pt.geometry) {
  73. return;
  74. }
  75. var key = pt.geometry.coordinates.join("-");
  76. if (!Object.prototype.hasOwnProperty.call(existing, key)) {
  77. cleaned.push(pt);
  78. existing[key] = true;
  79. }
  80. });
  81. return featureCollection(cleaned);
  82. }
  83. export default concave;