index.js 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import clone from "@turf/clone";
  2. import distance from "@turf/distance";
  3. import { coordAll } from "@turf/meta";
  4. import { convertLength, } from "@turf/helpers";
  5. import clustering from "density-clustering";
  6. /**
  7. * Takes a set of {@link Point|points} and partition them into clusters according to {@link DBSCAN's|https://en.wikipedia.org/wiki/DBSCAN} data clustering algorithm.
  8. *
  9. * @name clustersDbscan
  10. * @param {FeatureCollection<Point>} points to be clustered
  11. * @param {number} maxDistance Maximum Distance between any point of the cluster to generate the clusters (kilometers only)
  12. * @param {Object} [options={}] Optional parameters
  13. * @param {string} [options.units="kilometers"] in which `maxDistance` is expressed, can be degrees, radians, miles, or kilometers
  14. * @param {boolean} [options.mutate=false] Allows GeoJSON input to be mutated
  15. * @param {number} [options.minPoints=3] Minimum number of points to generate a single cluster,
  16. * points which do not meet this requirement will be classified as an 'edge' or 'noise'.
  17. * @returns {FeatureCollection<Point>} Clustered Points with an additional two properties associated to each Feature:
  18. * - {number} cluster - the associated clusterId
  19. * - {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')
  20. * @example
  21. * // create random points with random z-values in their properties
  22. * var points = turf.randomPoint(100, {bbox: [0, 30, 20, 50]});
  23. * var maxDistance = 100;
  24. * var clustered = turf.clustersDbscan(points, maxDistance);
  25. *
  26. * //addToMap
  27. * var addToMap = [clustered];
  28. */
  29. function clustersDbscan(points, maxDistance, options) {
  30. // Input validation being handled by Typescript
  31. // collectionOf(points, 'Point', 'points must consist of a FeatureCollection of only Points');
  32. // if (maxDistance === null || maxDistance === undefined) throw new Error('maxDistance is required');
  33. // if (!(Math.sign(maxDistance) > 0)) throw new Error('maxDistance is invalid');
  34. // if (!(minPoints === undefined || minPoints === null || Math.sign(minPoints) > 0)) throw new Error('options.minPoints is invalid');
  35. if (options === void 0) { options = {}; }
  36. // Clone points to prevent any mutations
  37. if (options.mutate !== true)
  38. points = clone(points);
  39. // Defaults
  40. options.minPoints = options.minPoints || 3;
  41. // create clustered ids
  42. var dbscan = new clustering.DBSCAN();
  43. var clusteredIds = dbscan.run(coordAll(points), convertLength(maxDistance, options.units), options.minPoints, distance);
  44. // Tag points to Clusters ID
  45. var clusterId = -1;
  46. clusteredIds.forEach(function (clusterIds) {
  47. clusterId++;
  48. // assign cluster ids to input points
  49. clusterIds.forEach(function (idx) {
  50. var clusterPoint = points.features[idx];
  51. if (!clusterPoint.properties)
  52. clusterPoint.properties = {};
  53. clusterPoint.properties.cluster = clusterId;
  54. clusterPoint.properties.dbscan = "core";
  55. });
  56. });
  57. // handle noise points, if any
  58. // edges points are tagged by DBSCAN as both 'noise' and 'cluster' as they can "reach" less than 'minPoints' number of points
  59. dbscan.noise.forEach(function (noiseId) {
  60. var noisePoint = points.features[noiseId];
  61. if (!noisePoint.properties)
  62. noisePoint.properties = {};
  63. if (noisePoint.properties.cluster)
  64. noisePoint.properties.dbscan = "edge";
  65. else
  66. noisePoint.properties.dbscan = "noise";
  67. });
  68. return points;
  69. }
  70. export default clustersDbscan;