@turf/clusters-dbscan 7.1.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,19 +2,38 @@
2
2
 
3
3
  <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
4
4
 
5
+ ## Dbscan
6
+
7
+ Point classification within the cluster.
8
+
9
+ Type: (`"core"` | `"edge"` | `"noise"`)
10
+
11
+ ## DbscanProps
12
+
13
+ **Extends GeoJsonProperties**
14
+
15
+ Properties assigned to each clustered point.
16
+
17
+ Type: [object][1]
18
+
19
+ ### Properties
20
+
21
+ * `dbscan` **[Dbscan][2]?** type of point it has been classified as
22
+ * `cluster` **[number][3]?** associated clusterId
23
+
5
24
  ## clustersDbscan
6
25
 
7
- Takes a set of [points][1] and partition them into clusters according to [https://en.wikipedia.org/wiki/DBSCAN][2] data clustering algorithm.
26
+ Takes a set of [points][4] and partition them into clusters according to [DBSCAN's][5] data clustering algorithm.
8
27
 
9
28
  ### Parameters
10
29
 
11
- * `points` **[FeatureCollection][3]<[Point][1]>** to be clustered
12
- * `maxDistance` **[number][4]** Maximum Distance between any point of the cluster to generate the clusters (kilometers by default, see options)
13
- * `options` **[Object][5]** Optional parameters (optional, default `{}`)
30
+ * `points` **[FeatureCollection][6]<[Point][4]>** to be clustered
31
+ * `maxDistance` **[number][3]** Maximum Distance between any point of the cluster to generate the clusters (kilometers by default, see options)
32
+ * `options` **[Object][1]** Optional parameters (optional, default `{}`)
14
33
 
15
- * `options.units` **[string][6]** in which `maxDistance` is expressed, can be degrees, radians, miles, or kilometers (optional, default `"kilometers"`)
16
- * `options.mutate` **[boolean][7]** Allows GeoJSON input to be mutated (optional, default `false`)
17
- * `options.minPoints` **[number][4]** Minimum number of points to generate a single cluster,
34
+ * `options.units` **Units** in which `maxDistance` is expressed, Supports all valid Turf [Units][7] (optional, default `"kilometers"`)
35
+ * `options.mutate` **[boolean][8]** Allows GeoJSON input to be mutated (optional, default `false`)
36
+ * `options.minPoints` **[number][3]** Minimum number of points to generate a single cluster,
18
37
  points which do not meet this requirement will be classified as an 'edge' or 'noise'. (optional, default `3`)
19
38
 
20
39
  ### Examples
@@ -29,22 +48,26 @@ var clustered = turf.clustersDbscan(points, maxDistance);
29
48
  var addToMap = [clustered];
30
49
  ```
31
50
 
32
- Returns **[FeatureCollection][3]<[Point][1]>** Clustered Points with an additional two properties associated to each Feature:* {number} cluster - the associated clusterId
51
+ Returns **[FeatureCollection][6]<[Point][4], [DbscanProps][9]>** Clustered Points with an additional two properties associated to each Feature:* {number} cluster - the associated clusterId
33
52
  * {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')
34
53
 
35
- [1]: https://tools.ietf.org/html/rfc7946#section-3.1.2
54
+ [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
55
+
56
+ [2]: #dbscan
57
+
58
+ [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
36
59
 
37
- [2]: DBSCAN's
60
+ [4]: https://tools.ietf.org/html/rfc7946#section-3.1.2
38
61
 
39
- [3]: https://tools.ietf.org/html/rfc7946#section-3.3
62
+ [5]: https://en.wikipedia.org/wiki/DBSCAN
40
63
 
41
- [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
64
+ [6]: https://tools.ietf.org/html/rfc7946#section-3.3
42
65
 
43
- [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
66
+ [7]: https://turfjs.org/docs/api/types/Units
44
67
 
45
- [6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
68
+ [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
46
69
 
47
- [7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
70
+ [9]: #dbscanprops
48
71
 
49
72
  <!-- This file is automatically generated. Please don't edit it directly. If you find an error, edit the source file of the module in question (likely index.js or index.ts), and re-run "yarn docs" from the root of the turf project. -->
50
73
 
@@ -2,18 +2,12 @@
2
2
  var _clone = require('@turf/clone');
3
3
  var _distance = require('@turf/distance');
4
4
  var _helpers = require('@turf/helpers');
5
-
6
- // lib/rbush-export.ts
7
5
  var _rbush = require('rbush'); var _rbush2 = _interopRequireDefault(_rbush);
8
- var rbush = _rbush2.default;
9
-
10
- // index.ts
11
6
  function clustersDbscan(points, maxDistance, options = {}) {
12
- if (options.mutate !== true)
13
- points = _clone.clone.call(void 0, points);
7
+ if (options.mutate !== true) points = _clone.clone.call(void 0, points);
14
8
  const minPoints = options.minPoints || 3;
15
9
  const latDistanceInDegrees = _helpers.lengthToDegrees.call(void 0, maxDistance, options.units);
16
- var tree = new rbush(points.features.length);
10
+ var tree = new (0, _rbush2.default)(points.features.length);
17
11
  var visited = points.features.map((_) => false);
18
12
  var assigned = points.features.map((_) => false);
19
13
  var isnoise = points.features.map((_) => false);
@@ -78,8 +72,7 @@ function clustersDbscan(points, maxDistance, options = {}) {
78
72
  };
79
73
  var nextClusteredId = 0;
80
74
  points.features.forEach((_, index) => {
81
- if (visited[index])
82
- return;
75
+ if (visited[index]) return;
83
76
  const neighbors = regionQuery(index);
84
77
  if (neighbors.length >= minPoints) {
85
78
  const clusteredId = nextClusteredId;
@@ -104,9 +97,9 @@ function clustersDbscan(points, maxDistance, options = {}) {
104
97
  });
105
98
  return points;
106
99
  }
107
- var turf_clusters_dbscan_default = clustersDbscan;
100
+ var index_default = clustersDbscan;
108
101
 
109
102
 
110
103
 
111
- exports.clustersDbscan = clustersDbscan; exports.default = turf_clusters_dbscan_default;
104
+ exports.clustersDbscan = clustersDbscan; exports.default = index_default;
112
105
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../index.ts","../../lib/rbush-export.ts"],"names":[],"mappings":"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","sourcesContent":["import { GeoJsonProperties, FeatureCollection, Point } from \"geojson\";\nimport { clone } from \"@turf/clone\";\nimport { distance } from \"@turf/distance\";\nimport { degreesToRadians, lengthToDegrees, Units } from \"@turf/helpers\";\nimport { rbush as RBush } from \"./lib/rbush-export.js\";\n\ntype Dbscan = \"core\" | \"edge\" | \"noise\";\ntype DbscanProps = GeoJsonProperties & {\n dbscan?: Dbscan;\n cluster?: number;\n};\n\n// Structure of a point in the spatial index\ntype IndexedPoint = {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n index: number;\n};\n\n/**\n * 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.\n *\n * @name clustersDbscan\n * @param {FeatureCollection<Point>} points to be clustered\n * @param {number} maxDistance Maximum Distance between any point of the cluster to generate the clusters (kilometers by default, see options)\n * @param {Object} [options={}] Optional parameters\n * @param {string} [options.units=\"kilometers\"] in which `maxDistance` is expressed, can be degrees, radians, miles, or kilometers\n * @param {boolean} [options.mutate=false] Allows GeoJSON input to be mutated\n * @param {number} [options.minPoints=3] Minimum number of points to generate a single cluster,\n * points which do not meet this requirement will be classified as an 'edge' or 'noise'.\n * @returns {FeatureCollection<Point>} Clustered Points with an additional two properties associated to each Feature:\n * - {number} cluster - the associated clusterId\n * - {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')\n * @example\n * // create random points with random z-values in their properties\n * var points = turf.randomPoint(100, {bbox: [0, 30, 20, 50]});\n * var maxDistance = 100;\n * var clustered = turf.clustersDbscan(points, maxDistance);\n *\n * //addToMap\n * var addToMap = [clustered];\n */\nfunction clustersDbscan(\n points: FeatureCollection<Point>,\n maxDistance: number,\n options: {\n units?: Units;\n minPoints?: number;\n mutate?: boolean;\n } = {}\n): FeatureCollection<Point, DbscanProps> {\n // Input validation being handled by Typescript\n // collectionOf(points, 'Point', 'points must consist of a FeatureCollection of only Points');\n // if (maxDistance === null || maxDistance === undefined) throw new Error('maxDistance is required');\n // if (!(Math.sign(maxDistance) > 0)) throw new Error('maxDistance is invalid');\n // if (!(minPoints === undefined || minPoints === null || Math.sign(minPoints) > 0)) throw new Error('options.minPoints is invalid');\n\n // Clone points to prevent any mutations\n if (options.mutate !== true) points = clone(points);\n\n // Defaults\n const minPoints = options.minPoints || 3;\n\n // Calculate the distance in degrees for region queries\n const latDistanceInDegrees = lengthToDegrees(maxDistance, options.units);\n\n // Create a spatial index\n var tree = new RBush(points.features.length);\n\n // Keeps track of whether a point has been visited or not.\n var visited = points.features.map((_) => false);\n\n // Keeps track of whether a point is assigned to a cluster or not.\n var assigned = points.features.map((_) => false);\n\n // Keeps track of whether a point is noise|edge or not.\n var isnoise = points.features.map((_) => false);\n\n // Keeps track of the clusterId for each point\n var clusterIds: number[] = points.features.map((_) => -1);\n\n // Index each point for spatial queries\n tree.load(\n points.features.map((point, index) => {\n var [x, y] = point.geometry.coordinates;\n return {\n minX: x,\n minY: y,\n maxX: x,\n maxY: y,\n index: index,\n } as IndexedPoint;\n })\n );\n\n // Function to find neighbors of a point within a given distance\n const regionQuery = (index: number): IndexedPoint[] => {\n const point = points.features[index];\n const [x, y] = point.geometry.coordinates;\n\n const minY = Math.max(y - latDistanceInDegrees, -90.0);\n const maxY = Math.min(y + latDistanceInDegrees, 90.0);\n\n const lonDistanceInDegrees = (function () {\n // Handle the case where the bounding box crosses the poles\n if (minY < 0 && maxY > 0) {\n return latDistanceInDegrees;\n }\n if (Math.abs(minY) < Math.abs(maxY)) {\n return latDistanceInDegrees / Math.cos(degreesToRadians(maxY));\n } else {\n return latDistanceInDegrees / Math.cos(degreesToRadians(minY));\n }\n })();\n\n const minX = Math.max(x - lonDistanceInDegrees, -360.0);\n const maxX = Math.min(x + lonDistanceInDegrees, 360.0);\n\n // Calculate the bounding box for the region query\n const bbox = { minX, minY, maxX, maxY };\n return (tree.search(bbox) as ReadonlyArray<IndexedPoint>).filter(\n (neighbor) => {\n const neighborIndex = neighbor.index;\n const neighborPoint = points.features[neighborIndex];\n const distanceInKm = distance(point, neighborPoint, {\n units: \"kilometers\",\n });\n return distanceInKm <= maxDistance;\n }\n );\n };\n\n // Function to expand a cluster\n const expandCluster = (clusteredId: number, neighbors: IndexedPoint[]) => {\n for (var i = 0; i < neighbors.length; i++) {\n var neighbor = neighbors[i];\n const neighborIndex = neighbor.index;\n if (!visited[neighborIndex]) {\n visited[neighborIndex] = true;\n const nextNeighbors = regionQuery(neighborIndex);\n if (nextNeighbors.length >= minPoints) {\n neighbors.push(...nextNeighbors);\n }\n }\n if (!assigned[neighborIndex]) {\n assigned[neighborIndex] = true;\n clusterIds[neighborIndex] = clusteredId;\n }\n }\n };\n\n // Main DBSCAN clustering algorithm\n var nextClusteredId = 0;\n points.features.forEach((_, index) => {\n if (visited[index]) return;\n const neighbors = regionQuery(index);\n if (neighbors.length >= minPoints) {\n const clusteredId = nextClusteredId;\n nextClusteredId++;\n visited[index] = true;\n expandCluster(clusteredId, neighbors);\n } else {\n isnoise[index] = true;\n }\n });\n\n // Assign DBSCAN properties to each point\n points.features.forEach((_, index) => {\n var clusterPoint = points.features[index];\n if (!clusterPoint.properties) {\n clusterPoint.properties = {};\n }\n\n if (clusterIds[index] >= 0) {\n clusterPoint.properties.dbscan = isnoise[index] ? \"edge\" : \"core\";\n clusterPoint.properties.cluster = clusterIds[index];\n } else {\n clusterPoint.properties.dbscan = \"noise\";\n }\n });\n\n return points as FeatureCollection<Point, DbscanProps>;\n}\n\nexport { Dbscan, DbscanProps, clustersDbscan };\nexport default clustersDbscan;\n","// Get around problems with moduleResolution node16 and some older libraries.\n// Manifests as \"This expression is not callable ... has no call signatures\"\n// https://stackoverflow.com/a/74709714\n\nimport lib from \"rbush\";\n\nexport const rbush = lib as unknown as typeof lib.default;\n"]}
1
+ {"version":3,"sources":["/home/runner/work/turf/turf/packages/turf-clusters-dbscan/dist/cjs/index.cjs","../../index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/turf/turf/packages/turf-clusters-dbscan/dist/cjs/index.cjs","sourcesContent":[null,"import { GeoJsonProperties, FeatureCollection, Point } from \"geojson\";\nimport { clone } from \"@turf/clone\";\nimport { distance } from \"@turf/distance\";\nimport { degreesToRadians, lengthToDegrees, Units } from \"@turf/helpers\";\nimport RBush from \"rbush\";\n\n/**\n * Point classification within the cluster.\n *\n * @typedef {\"core\" | \"edge\" | \"noise\"} Dbscan\n */\ntype Dbscan = \"core\" | \"edge\" | \"noise\";\n\n/**\n * Properties assigned to each clustered point.\n *\n * @extends GeoJsonProperties\n * @typedef {object} DbscanProps\n * @property {Dbscan} [dbscan] type of point it has been classified as\n * @property {number} [cluster] associated clusterId\n */\ntype DbscanProps = GeoJsonProperties & {\n dbscan?: Dbscan;\n cluster?: number;\n};\n\n// Structure of a point in the spatial index\ntype IndexedPoint = {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n index: number;\n};\n\n/**\n * Takes a set of {@link Point|points} and partition them into clusters according to {@link https://en.wikipedia.org/wiki/DBSCAN|DBSCAN's} data clustering algorithm.\n *\n * @function\n * @param {FeatureCollection<Point>} points to be clustered\n * @param {number} maxDistance Maximum Distance between any point of the cluster to generate the clusters (kilometers by default, see options)\n * @param {Object} [options={}] Optional parameters\n * @param {Units} [options.units=\"kilometers\"] in which `maxDistance` is expressed, Supports all valid Turf {@link https://turfjs.org/docs/api/types/Units Units}\n * @param {boolean} [options.mutate=false] Allows GeoJSON input to be mutated\n * @param {number} [options.minPoints=3] Minimum number of points to generate a single cluster,\n * points which do not meet this requirement will be classified as an 'edge' or 'noise'.\n * @returns {FeatureCollection<Point, DbscanProps>} Clustered Points with an additional two properties associated to each Feature:\n * - {number} cluster - the associated clusterId\n * - {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')\n * @example\n * // create random points with random z-values in their properties\n * var points = turf.randomPoint(100, {bbox: [0, 30, 20, 50]});\n * var maxDistance = 100;\n * var clustered = turf.clustersDbscan(points, maxDistance);\n *\n * //addToMap\n * var addToMap = [clustered];\n */\nfunction clustersDbscan(\n points: FeatureCollection<Point>,\n maxDistance: number,\n options: {\n units?: Units;\n minPoints?: number;\n mutate?: boolean;\n } = {}\n): FeatureCollection<Point, DbscanProps> {\n // Input validation being handled by Typescript\n // collectionOf(points, 'Point', 'points must consist of a FeatureCollection of only Points');\n // if (maxDistance === null || maxDistance === undefined) throw new Error('maxDistance is required');\n // if (!(Math.sign(maxDistance) > 0)) throw new Error('maxDistance is invalid');\n // if (!(minPoints === undefined || minPoints === null || Math.sign(minPoints) > 0)) throw new Error('options.minPoints is invalid');\n\n // Clone points to prevent any mutations\n if (options.mutate !== true) points = clone(points);\n\n // Defaults\n const minPoints = options.minPoints || 3;\n\n // Calculate the distance in degrees for region queries\n const latDistanceInDegrees = lengthToDegrees(maxDistance, options.units);\n\n // Create a spatial index\n var tree = new RBush(points.features.length);\n\n // Keeps track of whether a point has been visited or not.\n var visited = points.features.map((_) => false);\n\n // Keeps track of whether a point is assigned to a cluster or not.\n var assigned = points.features.map((_) => false);\n\n // Keeps track of whether a point is noise|edge or not.\n var isnoise = points.features.map((_) => false);\n\n // Keeps track of the clusterId for each point\n var clusterIds: number[] = points.features.map((_) => -1);\n\n // Index each point for spatial queries\n tree.load(\n points.features.map((point, index) => {\n var [x, y] = point.geometry.coordinates;\n return {\n minX: x,\n minY: y,\n maxX: x,\n maxY: y,\n index: index,\n } as IndexedPoint;\n })\n );\n\n // Function to find neighbors of a point within a given distance\n const regionQuery = (index: number): IndexedPoint[] => {\n const point = points.features[index];\n const [x, y] = point.geometry.coordinates;\n\n const minY = Math.max(y - latDistanceInDegrees, -90.0);\n const maxY = Math.min(y + latDistanceInDegrees, 90.0);\n\n const lonDistanceInDegrees = (function () {\n // Handle the case where the bounding box crosses the poles\n if (minY < 0 && maxY > 0) {\n return latDistanceInDegrees;\n }\n if (Math.abs(minY) < Math.abs(maxY)) {\n return latDistanceInDegrees / Math.cos(degreesToRadians(maxY));\n } else {\n return latDistanceInDegrees / Math.cos(degreesToRadians(minY));\n }\n })();\n\n const minX = Math.max(x - lonDistanceInDegrees, -360.0);\n const maxX = Math.min(x + lonDistanceInDegrees, 360.0);\n\n // Calculate the bounding box for the region query\n const bbox = { minX, minY, maxX, maxY };\n return (tree.search(bbox) as ReadonlyArray<IndexedPoint>).filter(\n (neighbor) => {\n const neighborIndex = neighbor.index;\n const neighborPoint = points.features[neighborIndex];\n const distanceInKm = distance(point, neighborPoint, {\n units: \"kilometers\",\n });\n return distanceInKm <= maxDistance;\n }\n );\n };\n\n // Function to expand a cluster\n const expandCluster = (clusteredId: number, neighbors: IndexedPoint[]) => {\n for (var i = 0; i < neighbors.length; i++) {\n var neighbor = neighbors[i];\n const neighborIndex = neighbor.index;\n if (!visited[neighborIndex]) {\n visited[neighborIndex] = true;\n const nextNeighbors = regionQuery(neighborIndex);\n if (nextNeighbors.length >= minPoints) {\n neighbors.push(...nextNeighbors);\n }\n }\n if (!assigned[neighborIndex]) {\n assigned[neighborIndex] = true;\n clusterIds[neighborIndex] = clusteredId;\n }\n }\n };\n\n // Main DBSCAN clustering algorithm\n var nextClusteredId = 0;\n points.features.forEach((_, index) => {\n if (visited[index]) return;\n const neighbors = regionQuery(index);\n if (neighbors.length >= minPoints) {\n const clusteredId = nextClusteredId;\n nextClusteredId++;\n visited[index] = true;\n expandCluster(clusteredId, neighbors);\n } else {\n isnoise[index] = true;\n }\n });\n\n // Assign DBSCAN properties to each point\n points.features.forEach((_, index) => {\n var clusterPoint = points.features[index];\n if (!clusterPoint.properties) {\n clusterPoint.properties = {};\n }\n\n if (clusterIds[index] >= 0) {\n clusterPoint.properties.dbscan = isnoise[index] ? \"edge\" : \"core\";\n clusterPoint.properties.cluster = clusterIds[index];\n } else {\n clusterPoint.properties.dbscan = \"noise\";\n }\n });\n\n return points as FeatureCollection<Point, DbscanProps>;\n}\n\nexport { Dbscan, DbscanProps, clustersDbscan };\nexport default clustersDbscan;\n"]}
@@ -1,23 +1,36 @@
1
1
  import { GeoJsonProperties, FeatureCollection, Point } from 'geojson';
2
2
  import { Units } from '@turf/helpers';
3
3
 
4
+ /**
5
+ * Point classification within the cluster.
6
+ *
7
+ * @typedef {"core" | "edge" | "noise"} Dbscan
8
+ */
4
9
  type Dbscan = "core" | "edge" | "noise";
10
+ /**
11
+ * Properties assigned to each clustered point.
12
+ *
13
+ * @extends GeoJsonProperties
14
+ * @typedef {object} DbscanProps
15
+ * @property {Dbscan} [dbscan] type of point it has been classified as
16
+ * @property {number} [cluster] associated clusterId
17
+ */
5
18
  type DbscanProps = GeoJsonProperties & {
6
19
  dbscan?: Dbscan;
7
20
  cluster?: number;
8
21
  };
9
22
  /**
10
- * 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.
23
+ * Takes a set of {@link Point|points} and partition them into clusters according to {@link https://en.wikipedia.org/wiki/DBSCAN|DBSCAN's} data clustering algorithm.
11
24
  *
12
- * @name clustersDbscan
25
+ * @function
13
26
  * @param {FeatureCollection<Point>} points to be clustered
14
27
  * @param {number} maxDistance Maximum Distance between any point of the cluster to generate the clusters (kilometers by default, see options)
15
28
  * @param {Object} [options={}] Optional parameters
16
- * @param {string} [options.units="kilometers"] in which `maxDistance` is expressed, can be degrees, radians, miles, or kilometers
29
+ * @param {Units} [options.units="kilometers"] in which `maxDistance` is expressed, Supports all valid Turf {@link https://turfjs.org/docs/api/types/Units Units}
17
30
  * @param {boolean} [options.mutate=false] Allows GeoJSON input to be mutated
18
31
  * @param {number} [options.minPoints=3] Minimum number of points to generate a single cluster,
19
32
  * points which do not meet this requirement will be classified as an 'edge' or 'noise'.
20
- * @returns {FeatureCollection<Point>} Clustered Points with an additional two properties associated to each Feature:
33
+ * @returns {FeatureCollection<Point, DbscanProps>} Clustered Points with an additional two properties associated to each Feature:
21
34
  * - {number} cluster - the associated clusterId
22
35
  * - {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')
23
36
  * @example
@@ -1,23 +1,36 @@
1
1
  import { GeoJsonProperties, FeatureCollection, Point } from 'geojson';
2
2
  import { Units } from '@turf/helpers';
3
3
 
4
+ /**
5
+ * Point classification within the cluster.
6
+ *
7
+ * @typedef {"core" | "edge" | "noise"} Dbscan
8
+ */
4
9
  type Dbscan = "core" | "edge" | "noise";
10
+ /**
11
+ * Properties assigned to each clustered point.
12
+ *
13
+ * @extends GeoJsonProperties
14
+ * @typedef {object} DbscanProps
15
+ * @property {Dbscan} [dbscan] type of point it has been classified as
16
+ * @property {number} [cluster] associated clusterId
17
+ */
5
18
  type DbscanProps = GeoJsonProperties & {
6
19
  dbscan?: Dbscan;
7
20
  cluster?: number;
8
21
  };
9
22
  /**
10
- * 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.
23
+ * Takes a set of {@link Point|points} and partition them into clusters according to {@link https://en.wikipedia.org/wiki/DBSCAN|DBSCAN's} data clustering algorithm.
11
24
  *
12
- * @name clustersDbscan
25
+ * @function
13
26
  * @param {FeatureCollection<Point>} points to be clustered
14
27
  * @param {number} maxDistance Maximum Distance between any point of the cluster to generate the clusters (kilometers by default, see options)
15
28
  * @param {Object} [options={}] Optional parameters
16
- * @param {string} [options.units="kilometers"] in which `maxDistance` is expressed, can be degrees, radians, miles, or kilometers
29
+ * @param {Units} [options.units="kilometers"] in which `maxDistance` is expressed, Supports all valid Turf {@link https://turfjs.org/docs/api/types/Units Units}
17
30
  * @param {boolean} [options.mutate=false] Allows GeoJSON input to be mutated
18
31
  * @param {number} [options.minPoints=3] Minimum number of points to generate a single cluster,
19
32
  * points which do not meet this requirement will be classified as an 'edge' or 'noise'.
20
- * @returns {FeatureCollection<Point>} Clustered Points with an additional two properties associated to each Feature:
33
+ * @returns {FeatureCollection<Point, DbscanProps>} Clustered Points with an additional two properties associated to each Feature:
21
34
  * - {number} cluster - the associated clusterId
22
35
  * - {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')
23
36
  * @example
package/dist/esm/index.js CHANGED
@@ -2,18 +2,12 @@
2
2
  import { clone } from "@turf/clone";
3
3
  import { distance } from "@turf/distance";
4
4
  import { degreesToRadians, lengthToDegrees } from "@turf/helpers";
5
-
6
- // lib/rbush-export.ts
7
- import lib from "rbush";
8
- var rbush = lib;
9
-
10
- // index.ts
5
+ import RBush from "rbush";
11
6
  function clustersDbscan(points, maxDistance, options = {}) {
12
- if (options.mutate !== true)
13
- points = clone(points);
7
+ if (options.mutate !== true) points = clone(points);
14
8
  const minPoints = options.minPoints || 3;
15
9
  const latDistanceInDegrees = lengthToDegrees(maxDistance, options.units);
16
- var tree = new rbush(points.features.length);
10
+ var tree = new RBush(points.features.length);
17
11
  var visited = points.features.map((_) => false);
18
12
  var assigned = points.features.map((_) => false);
19
13
  var isnoise = points.features.map((_) => false);
@@ -78,8 +72,7 @@ function clustersDbscan(points, maxDistance, options = {}) {
78
72
  };
79
73
  var nextClusteredId = 0;
80
74
  points.features.forEach((_, index) => {
81
- if (visited[index])
82
- return;
75
+ if (visited[index]) return;
83
76
  const neighbors = regionQuery(index);
84
77
  if (neighbors.length >= minPoints) {
85
78
  const clusteredId = nextClusteredId;
@@ -104,9 +97,9 @@ function clustersDbscan(points, maxDistance, options = {}) {
104
97
  });
105
98
  return points;
106
99
  }
107
- var turf_clusters_dbscan_default = clustersDbscan;
100
+ var index_default = clustersDbscan;
108
101
  export {
109
102
  clustersDbscan,
110
- turf_clusters_dbscan_default as default
103
+ index_default as default
111
104
  };
112
105
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../index.ts","../../lib/rbush-export.ts"],"sourcesContent":["import { GeoJsonProperties, FeatureCollection, Point } from \"geojson\";\nimport { clone } from \"@turf/clone\";\nimport { distance } from \"@turf/distance\";\nimport { degreesToRadians, lengthToDegrees, Units } from \"@turf/helpers\";\nimport { rbush as RBush } from \"./lib/rbush-export.js\";\n\ntype Dbscan = \"core\" | \"edge\" | \"noise\";\ntype DbscanProps = GeoJsonProperties & {\n dbscan?: Dbscan;\n cluster?: number;\n};\n\n// Structure of a point in the spatial index\ntype IndexedPoint = {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n index: number;\n};\n\n/**\n * 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.\n *\n * @name clustersDbscan\n * @param {FeatureCollection<Point>} points to be clustered\n * @param {number} maxDistance Maximum Distance between any point of the cluster to generate the clusters (kilometers by default, see options)\n * @param {Object} [options={}] Optional parameters\n * @param {string} [options.units=\"kilometers\"] in which `maxDistance` is expressed, can be degrees, radians, miles, or kilometers\n * @param {boolean} [options.mutate=false] Allows GeoJSON input to be mutated\n * @param {number} [options.minPoints=3] Minimum number of points to generate a single cluster,\n * points which do not meet this requirement will be classified as an 'edge' or 'noise'.\n * @returns {FeatureCollection<Point>} Clustered Points with an additional two properties associated to each Feature:\n * - {number} cluster - the associated clusterId\n * - {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')\n * @example\n * // create random points with random z-values in their properties\n * var points = turf.randomPoint(100, {bbox: [0, 30, 20, 50]});\n * var maxDistance = 100;\n * var clustered = turf.clustersDbscan(points, maxDistance);\n *\n * //addToMap\n * var addToMap = [clustered];\n */\nfunction clustersDbscan(\n points: FeatureCollection<Point>,\n maxDistance: number,\n options: {\n units?: Units;\n minPoints?: number;\n mutate?: boolean;\n } = {}\n): FeatureCollection<Point, DbscanProps> {\n // Input validation being handled by Typescript\n // collectionOf(points, 'Point', 'points must consist of a FeatureCollection of only Points');\n // if (maxDistance === null || maxDistance === undefined) throw new Error('maxDistance is required');\n // if (!(Math.sign(maxDistance) > 0)) throw new Error('maxDistance is invalid');\n // if (!(minPoints === undefined || minPoints === null || Math.sign(minPoints) > 0)) throw new Error('options.minPoints is invalid');\n\n // Clone points to prevent any mutations\n if (options.mutate !== true) points = clone(points);\n\n // Defaults\n const minPoints = options.minPoints || 3;\n\n // Calculate the distance in degrees for region queries\n const latDistanceInDegrees = lengthToDegrees(maxDistance, options.units);\n\n // Create a spatial index\n var tree = new RBush(points.features.length);\n\n // Keeps track of whether a point has been visited or not.\n var visited = points.features.map((_) => false);\n\n // Keeps track of whether a point is assigned to a cluster or not.\n var assigned = points.features.map((_) => false);\n\n // Keeps track of whether a point is noise|edge or not.\n var isnoise = points.features.map((_) => false);\n\n // Keeps track of the clusterId for each point\n var clusterIds: number[] = points.features.map((_) => -1);\n\n // Index each point for spatial queries\n tree.load(\n points.features.map((point, index) => {\n var [x, y] = point.geometry.coordinates;\n return {\n minX: x,\n minY: y,\n maxX: x,\n maxY: y,\n index: index,\n } as IndexedPoint;\n })\n );\n\n // Function to find neighbors of a point within a given distance\n const regionQuery = (index: number): IndexedPoint[] => {\n const point = points.features[index];\n const [x, y] = point.geometry.coordinates;\n\n const minY = Math.max(y - latDistanceInDegrees, -90.0);\n const maxY = Math.min(y + latDistanceInDegrees, 90.0);\n\n const lonDistanceInDegrees = (function () {\n // Handle the case where the bounding box crosses the poles\n if (minY < 0 && maxY > 0) {\n return latDistanceInDegrees;\n }\n if (Math.abs(minY) < Math.abs(maxY)) {\n return latDistanceInDegrees / Math.cos(degreesToRadians(maxY));\n } else {\n return latDistanceInDegrees / Math.cos(degreesToRadians(minY));\n }\n })();\n\n const minX = Math.max(x - lonDistanceInDegrees, -360.0);\n const maxX = Math.min(x + lonDistanceInDegrees, 360.0);\n\n // Calculate the bounding box for the region query\n const bbox = { minX, minY, maxX, maxY };\n return (tree.search(bbox) as ReadonlyArray<IndexedPoint>).filter(\n (neighbor) => {\n const neighborIndex = neighbor.index;\n const neighborPoint = points.features[neighborIndex];\n const distanceInKm = distance(point, neighborPoint, {\n units: \"kilometers\",\n });\n return distanceInKm <= maxDistance;\n }\n );\n };\n\n // Function to expand a cluster\n const expandCluster = (clusteredId: number, neighbors: IndexedPoint[]) => {\n for (var i = 0; i < neighbors.length; i++) {\n var neighbor = neighbors[i];\n const neighborIndex = neighbor.index;\n if (!visited[neighborIndex]) {\n visited[neighborIndex] = true;\n const nextNeighbors = regionQuery(neighborIndex);\n if (nextNeighbors.length >= minPoints) {\n neighbors.push(...nextNeighbors);\n }\n }\n if (!assigned[neighborIndex]) {\n assigned[neighborIndex] = true;\n clusterIds[neighborIndex] = clusteredId;\n }\n }\n };\n\n // Main DBSCAN clustering algorithm\n var nextClusteredId = 0;\n points.features.forEach((_, index) => {\n if (visited[index]) return;\n const neighbors = regionQuery(index);\n if (neighbors.length >= minPoints) {\n const clusteredId = nextClusteredId;\n nextClusteredId++;\n visited[index] = true;\n expandCluster(clusteredId, neighbors);\n } else {\n isnoise[index] = true;\n }\n });\n\n // Assign DBSCAN properties to each point\n points.features.forEach((_, index) => {\n var clusterPoint = points.features[index];\n if (!clusterPoint.properties) {\n clusterPoint.properties = {};\n }\n\n if (clusterIds[index] >= 0) {\n clusterPoint.properties.dbscan = isnoise[index] ? \"edge\" : \"core\";\n clusterPoint.properties.cluster = clusterIds[index];\n } else {\n clusterPoint.properties.dbscan = \"noise\";\n }\n });\n\n return points as FeatureCollection<Point, DbscanProps>;\n}\n\nexport { Dbscan, DbscanProps, clustersDbscan };\nexport default clustersDbscan;\n","// Get around problems with moduleResolution node16 and some older libraries.\n// Manifests as \"This expression is not callable ... has no call signatures\"\n// https://stackoverflow.com/a/74709714\n\nimport lib from \"rbush\";\n\nexport const rbush = lib as unknown as typeof lib.default;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../index.ts"],"sourcesContent":["import { GeoJsonProperties, FeatureCollection, Point } from \"geojson\";\nimport { clone } from \"@turf/clone\";\nimport { distance } from \"@turf/distance\";\nimport { degreesToRadians, lengthToDegrees, Units } from \"@turf/helpers\";\nimport RBush from \"rbush\";\n\n/**\n * Point classification within the cluster.\n *\n * @typedef {\"core\" | \"edge\" | \"noise\"} Dbscan\n */\ntype Dbscan = \"core\" | \"edge\" | \"noise\";\n\n/**\n * Properties assigned to each clustered point.\n *\n * @extends GeoJsonProperties\n * @typedef {object} DbscanProps\n * @property {Dbscan} [dbscan] type of point it has been classified as\n * @property {number} [cluster] associated clusterId\n */\ntype DbscanProps = GeoJsonProperties & {\n dbscan?: Dbscan;\n cluster?: number;\n};\n\n// Structure of a point in the spatial index\ntype IndexedPoint = {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n index: number;\n};\n\n/**\n * Takes a set of {@link Point|points} and partition them into clusters according to {@link https://en.wikipedia.org/wiki/DBSCAN|DBSCAN's} data clustering algorithm.\n *\n * @function\n * @param {FeatureCollection<Point>} points to be clustered\n * @param {number} maxDistance Maximum Distance between any point of the cluster to generate the clusters (kilometers by default, see options)\n * @param {Object} [options={}] Optional parameters\n * @param {Units} [options.units=\"kilometers\"] in which `maxDistance` is expressed, Supports all valid Turf {@link https://turfjs.org/docs/api/types/Units Units}\n * @param {boolean} [options.mutate=false] Allows GeoJSON input to be mutated\n * @param {number} [options.minPoints=3] Minimum number of points to generate a single cluster,\n * points which do not meet this requirement will be classified as an 'edge' or 'noise'.\n * @returns {FeatureCollection<Point, DbscanProps>} Clustered Points with an additional two properties associated to each Feature:\n * - {number} cluster - the associated clusterId\n * - {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')\n * @example\n * // create random points with random z-values in their properties\n * var points = turf.randomPoint(100, {bbox: [0, 30, 20, 50]});\n * var maxDistance = 100;\n * var clustered = turf.clustersDbscan(points, maxDistance);\n *\n * //addToMap\n * var addToMap = [clustered];\n */\nfunction clustersDbscan(\n points: FeatureCollection<Point>,\n maxDistance: number,\n options: {\n units?: Units;\n minPoints?: number;\n mutate?: boolean;\n } = {}\n): FeatureCollection<Point, DbscanProps> {\n // Input validation being handled by Typescript\n // collectionOf(points, 'Point', 'points must consist of a FeatureCollection of only Points');\n // if (maxDistance === null || maxDistance === undefined) throw new Error('maxDistance is required');\n // if (!(Math.sign(maxDistance) > 0)) throw new Error('maxDistance is invalid');\n // if (!(minPoints === undefined || minPoints === null || Math.sign(minPoints) > 0)) throw new Error('options.minPoints is invalid');\n\n // Clone points to prevent any mutations\n if (options.mutate !== true) points = clone(points);\n\n // Defaults\n const minPoints = options.minPoints || 3;\n\n // Calculate the distance in degrees for region queries\n const latDistanceInDegrees = lengthToDegrees(maxDistance, options.units);\n\n // Create a spatial index\n var tree = new RBush(points.features.length);\n\n // Keeps track of whether a point has been visited or not.\n var visited = points.features.map((_) => false);\n\n // Keeps track of whether a point is assigned to a cluster or not.\n var assigned = points.features.map((_) => false);\n\n // Keeps track of whether a point is noise|edge or not.\n var isnoise = points.features.map((_) => false);\n\n // Keeps track of the clusterId for each point\n var clusterIds: number[] = points.features.map((_) => -1);\n\n // Index each point for spatial queries\n tree.load(\n points.features.map((point, index) => {\n var [x, y] = point.geometry.coordinates;\n return {\n minX: x,\n minY: y,\n maxX: x,\n maxY: y,\n index: index,\n } as IndexedPoint;\n })\n );\n\n // Function to find neighbors of a point within a given distance\n const regionQuery = (index: number): IndexedPoint[] => {\n const point = points.features[index];\n const [x, y] = point.geometry.coordinates;\n\n const minY = Math.max(y - latDistanceInDegrees, -90.0);\n const maxY = Math.min(y + latDistanceInDegrees, 90.0);\n\n const lonDistanceInDegrees = (function () {\n // Handle the case where the bounding box crosses the poles\n if (minY < 0 && maxY > 0) {\n return latDistanceInDegrees;\n }\n if (Math.abs(minY) < Math.abs(maxY)) {\n return latDistanceInDegrees / Math.cos(degreesToRadians(maxY));\n } else {\n return latDistanceInDegrees / Math.cos(degreesToRadians(minY));\n }\n })();\n\n const minX = Math.max(x - lonDistanceInDegrees, -360.0);\n const maxX = Math.min(x + lonDistanceInDegrees, 360.0);\n\n // Calculate the bounding box for the region query\n const bbox = { minX, minY, maxX, maxY };\n return (tree.search(bbox) as ReadonlyArray<IndexedPoint>).filter(\n (neighbor) => {\n const neighborIndex = neighbor.index;\n const neighborPoint = points.features[neighborIndex];\n const distanceInKm = distance(point, neighborPoint, {\n units: \"kilometers\",\n });\n return distanceInKm <= maxDistance;\n }\n );\n };\n\n // Function to expand a cluster\n const expandCluster = (clusteredId: number, neighbors: IndexedPoint[]) => {\n for (var i = 0; i < neighbors.length; i++) {\n var neighbor = neighbors[i];\n const neighborIndex = neighbor.index;\n if (!visited[neighborIndex]) {\n visited[neighborIndex] = true;\n const nextNeighbors = regionQuery(neighborIndex);\n if (nextNeighbors.length >= minPoints) {\n neighbors.push(...nextNeighbors);\n }\n }\n if (!assigned[neighborIndex]) {\n assigned[neighborIndex] = true;\n clusterIds[neighborIndex] = clusteredId;\n }\n }\n };\n\n // Main DBSCAN clustering algorithm\n var nextClusteredId = 0;\n points.features.forEach((_, index) => {\n if (visited[index]) return;\n const neighbors = regionQuery(index);\n if (neighbors.length >= minPoints) {\n const clusteredId = nextClusteredId;\n nextClusteredId++;\n visited[index] = true;\n expandCluster(clusteredId, neighbors);\n } else {\n isnoise[index] = true;\n }\n });\n\n // Assign DBSCAN properties to each point\n points.features.forEach((_, index) => {\n var clusterPoint = points.features[index];\n if (!clusterPoint.properties) {\n clusterPoint.properties = {};\n }\n\n if (clusterIds[index] >= 0) {\n clusterPoint.properties.dbscan = isnoise[index] ? \"edge\" : \"core\";\n clusterPoint.properties.cluster = clusterIds[index];\n } else {\n clusterPoint.properties.dbscan = \"noise\";\n }\n });\n\n return points as FeatureCollection<Point, DbscanProps>;\n}\n\nexport { Dbscan, DbscanProps, clustersDbscan };\nexport default clustersDbscan;\n"],"mappings":";AACA,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB,SAAS,kBAAkB,uBAA8B;AACzD,OAAO,WAAW;AAsDlB,SAAS,eACP,QACA,aACA,UAII,CAAC,GACkC;AAQvC,MAAI,QAAQ,WAAW,KAAM,UAAS,MAAM,MAAM;AAGlD,QAAM,YAAY,QAAQ,aAAa;AAGvC,QAAM,uBAAuB,gBAAgB,aAAa,QAAQ,KAAK;AAGvE,MAAI,OAAO,IAAI,MAAM,OAAO,SAAS,MAAM;AAG3C,MAAI,UAAU,OAAO,SAAS,IAAI,CAAC,MAAM,KAAK;AAG9C,MAAI,WAAW,OAAO,SAAS,IAAI,CAAC,MAAM,KAAK;AAG/C,MAAI,UAAU,OAAO,SAAS,IAAI,CAAC,MAAM,KAAK;AAG9C,MAAI,aAAuB,OAAO,SAAS,IAAI,CAAC,MAAM,EAAE;AAGxD,OAAK;AAAA,IACH,OAAO,SAAS,IAAI,CAAC,OAAO,UAAU;AACpC,UAAI,CAAC,GAAG,CAAC,IAAI,MAAM,SAAS;AAC5B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,cAAc,CAAC,UAAkC;AACrD,UAAM,QAAQ,OAAO,SAAS,KAAK;AACnC,UAAM,CAAC,GAAG,CAAC,IAAI,MAAM,SAAS;AAE9B,UAAM,OAAO,KAAK,IAAI,IAAI,sBAAsB,GAAK;AACrD,UAAM,OAAO,KAAK,IAAI,IAAI,sBAAsB,EAAI;AAEpD,UAAM,uBAAwB,WAAY;AAExC,UAAI,OAAO,KAAK,OAAO,GAAG;AACxB,eAAO;AAAA,MACT;AACA,UAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG;AACnC,eAAO,uBAAuB,KAAK,IAAI,iBAAiB,IAAI,CAAC;AAAA,MAC/D,OAAO;AACL,eAAO,uBAAuB,KAAK,IAAI,iBAAiB,IAAI,CAAC;AAAA,MAC/D;AAAA,IACF,EAAG;AAEH,UAAM,OAAO,KAAK,IAAI,IAAI,sBAAsB,IAAM;AACtD,UAAM,OAAO,KAAK,IAAI,IAAI,sBAAsB,GAAK;AAGrD,UAAM,OAAO,EAAE,MAAM,MAAM,MAAM,KAAK;AACtC,WAAQ,KAAK,OAAO,IAAI,EAAkC;AAAA,MACxD,CAAC,aAAa;AACZ,cAAM,gBAAgB,SAAS;AAC/B,cAAM,gBAAgB,OAAO,SAAS,aAAa;AACnD,cAAM,eAAe,SAAS,OAAO,eAAe;AAAA,UAClD,OAAO;AAAA,QACT,CAAC;AACD,eAAO,gBAAgB;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgB,CAAC,aAAqB,cAA8B;AACxE,aAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,UAAI,WAAW,UAAU,CAAC;AAC1B,YAAM,gBAAgB,SAAS;AAC/B,UAAI,CAAC,QAAQ,aAAa,GAAG;AAC3B,gBAAQ,aAAa,IAAI;AACzB,cAAM,gBAAgB,YAAY,aAAa;AAC/C,YAAI,cAAc,UAAU,WAAW;AACrC,oBAAU,KAAK,GAAG,aAAa;AAAA,QACjC;AAAA,MACF;AACA,UAAI,CAAC,SAAS,aAAa,GAAG;AAC5B,iBAAS,aAAa,IAAI;AAC1B,mBAAW,aAAa,IAAI;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAGA,MAAI,kBAAkB;AACtB,SAAO,SAAS,QAAQ,CAAC,GAAG,UAAU;AACpC,QAAI,QAAQ,KAAK,EAAG;AACpB,UAAM,YAAY,YAAY,KAAK;AACnC,QAAI,UAAU,UAAU,WAAW;AACjC,YAAM,cAAc;AACpB;AACA,cAAQ,KAAK,IAAI;AACjB,oBAAc,aAAa,SAAS;AAAA,IACtC,OAAO;AACL,cAAQ,KAAK,IAAI;AAAA,IACnB;AAAA,EACF,CAAC;AAGD,SAAO,SAAS,QAAQ,CAAC,GAAG,UAAU;AACpC,QAAI,eAAe,OAAO,SAAS,KAAK;AACxC,QAAI,CAAC,aAAa,YAAY;AAC5B,mBAAa,aAAa,CAAC;AAAA,IAC7B;AAEA,QAAI,WAAW,KAAK,KAAK,GAAG;AAC1B,mBAAa,WAAW,SAAS,QAAQ,KAAK,IAAI,SAAS;AAC3D,mBAAa,WAAW,UAAU,WAAW,KAAK;AAAA,IACpD,OAAO;AACL,mBAAa,WAAW,SAAS;AAAA,IACnC;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAGA,IAAO,gBAAQ;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@turf/clusters-dbscan",
3
- "version": "7.1.0",
4
- "description": "turf clusters-dbscan module",
3
+ "version": "7.3.0",
4
+ "description": "Takes a set of points and partition them into clusters according to DBSCAN's data clustering algorithm.",
5
5
  "author": "Turf Authors",
6
6
  "contributors": [
7
7
  "Lukasz <@uhho>",
@@ -60,30 +60,30 @@
60
60
  "test:types": "tsc --esModuleInterop --module node16 --moduleResolution node16 --noEmit --strict types.ts"
61
61
  },
62
62
  "devDependencies": {
63
- "@turf/centroid": "^7.1.0",
64
- "@turf/clusters": "^7.1.0",
63
+ "@turf/centroid": "7.3.0",
64
+ "@turf/clusters": "7.3.0",
65
65
  "@types/benchmark": "^2.1.5",
66
- "@types/rbush": "^3.0.2",
67
- "@types/tape": "^4.2.32",
66
+ "@types/rbush": "^3.0.4",
67
+ "@types/tape": "^5.8.1",
68
68
  "benchmark": "^2.1.4",
69
69
  "chromatism": "^3.0.0",
70
70
  "concaveman": "^1.2.1",
71
71
  "load-json-file": "^7.0.1",
72
72
  "npm-run-all": "^4.1.5",
73
- "tape": "^5.7.2",
74
- "tsup": "^8.0.1",
75
- "tsx": "^4.6.2",
76
- "typescript": "^5.2.2",
77
- "write-json-file": "^5.0.0"
73
+ "tape": "^5.9.0",
74
+ "tsup": "^8.4.0",
75
+ "tsx": "^4.19.4",
76
+ "typescript": "^5.8.3",
77
+ "write-json-file": "^6.0.0"
78
78
  },
79
79
  "dependencies": {
80
- "@turf/clone": "^7.1.0",
81
- "@turf/distance": "^7.1.0",
82
- "@turf/helpers": "^7.1.0",
83
- "@turf/meta": "^7.1.0",
80
+ "@turf/clone": "7.3.0",
81
+ "@turf/distance": "7.3.0",
82
+ "@turf/helpers": "7.3.0",
83
+ "@turf/meta": "7.3.0",
84
84
  "@types/geojson": "^7946.0.10",
85
85
  "rbush": "^3.0.1",
86
- "tslib": "^2.6.2"
86
+ "tslib": "^2.8.1"
87
87
  },
88
- "gitHead": "68915eeebc9278bb40dec3f1034499698a0561ef"
88
+ "gitHead": "9f58a103e8f9a587ab640307ed03ba5233913ddd"
89
89
  }