@turf/clusters-dbscan 7.0.0-alpha.1 → 7.0.0-alpha.110

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
@@ -48,26 +48,21 @@ Returns **[FeatureCollection][3]<[Point][4]>** Clustered Points with an addition
48
48
 
49
49
  [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
50
50
 
51
- <!-- This file is automatically generated. Please don't edit it directly:
52
- if you find an error, edit the source file (likely index.js), and re-run
53
- ./scripts/generate-readmes in the turf project. -->
51
+ <!-- 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. -->
54
52
 
55
53
  ---
56
54
 
57
- This module is part of the [Turfjs project](http://turfjs.org/), an open source
58
- module collection dedicated to geographic algorithms. It is maintained in the
59
- [Turfjs/turf](https://github.com/Turfjs/turf) repository, where you can create
60
- PRs and issues.
55
+ This module is part of the [Turfjs project](https://turfjs.org/), an open source module collection dedicated to geographic algorithms. It is maintained in the [Turfjs/turf](https://github.com/Turfjs/turf) repository, where you can create PRs and issues.
61
56
 
62
57
  ### Installation
63
58
 
64
- Install this module individually:
59
+ Install this single module individually:
65
60
 
66
61
  ```sh
67
62
  $ npm install @turf/clusters-dbscan
68
63
  ```
69
64
 
70
- Or install the Turf module that includes it as a function:
65
+ Or install the all-encompassing @turf/turf module that includes all modules as functions:
71
66
 
72
67
  ```sh
73
68
  $ npm install @turf/turf
@@ -0,0 +1,111 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // index.ts
5
+ var _clone = require('@turf/clone');
6
+ var _distance = require('@turf/distance');
7
+ var _helpers = require('@turf/helpers');
8
+ var _rbush = require('rbush'); var _rbush2 = _interopRequireDefault(_rbush);
9
+ function clustersDbscan(points, maxDistance, options = {}) {
10
+ if (options.mutate !== true)
11
+ points = _clone.clone.call(void 0, points);
12
+ const minPoints = options.minPoints || 3;
13
+ const latDistanceInDegrees = _helpers.lengthToDegrees.call(void 0, maxDistance, options.units);
14
+ var tree = new (0, _rbush2.default)(points.features.length);
15
+ var visited = points.features.map((_) => false);
16
+ var assigned = points.features.map((_) => false);
17
+ var isnoise = points.features.map((_) => false);
18
+ var clusterIds = points.features.map((_) => -1);
19
+ tree.load(
20
+ points.features.map((point, index) => {
21
+ var [x, y] = point.geometry.coordinates;
22
+ return {
23
+ minX: x,
24
+ minY: y,
25
+ maxX: x,
26
+ maxY: y,
27
+ index
28
+ };
29
+ })
30
+ );
31
+ const regionQuery = /* @__PURE__ */ __name((index) => {
32
+ const point = points.features[index];
33
+ const [x, y] = point.geometry.coordinates;
34
+ const minY = Math.max(y - latDistanceInDegrees, -90);
35
+ const maxY = Math.min(y + latDistanceInDegrees, 90);
36
+ const lonDistanceInDegrees = function() {
37
+ if (minY < 0 && maxY > 0) {
38
+ return latDistanceInDegrees;
39
+ }
40
+ if (Math.abs(minY) < Math.abs(maxY)) {
41
+ return latDistanceInDegrees / Math.cos(_helpers.degreesToRadians.call(void 0, maxY));
42
+ } else {
43
+ return latDistanceInDegrees / Math.cos(_helpers.degreesToRadians.call(void 0, minY));
44
+ }
45
+ }();
46
+ const minX = Math.max(x - lonDistanceInDegrees, -360);
47
+ const maxX = Math.min(x + lonDistanceInDegrees, 360);
48
+ const bbox = { minX, minY, maxX, maxY };
49
+ return tree.search(bbox).filter(
50
+ (neighbor) => {
51
+ const neighborIndex = neighbor.index;
52
+ const neighborPoint = points.features[neighborIndex];
53
+ const distanceInKm = _distance.distance.call(void 0, point, neighborPoint, {
54
+ units: "kilometers"
55
+ });
56
+ return distanceInKm <= maxDistance;
57
+ }
58
+ );
59
+ }, "regionQuery");
60
+ const expandCluster = /* @__PURE__ */ __name((clusteredId, neighbors) => {
61
+ for (var i = 0; i < neighbors.length; i++) {
62
+ var neighbor = neighbors[i];
63
+ const neighborIndex = neighbor.index;
64
+ if (!visited[neighborIndex]) {
65
+ visited[neighborIndex] = true;
66
+ const nextNeighbors = regionQuery(neighborIndex);
67
+ if (nextNeighbors.length >= minPoints) {
68
+ neighbors.push(...nextNeighbors);
69
+ }
70
+ }
71
+ if (!assigned[neighborIndex]) {
72
+ assigned[neighborIndex] = true;
73
+ clusterIds[neighborIndex] = clusteredId;
74
+ }
75
+ }
76
+ }, "expandCluster");
77
+ var nextClusteredId = 0;
78
+ points.features.forEach((_, index) => {
79
+ if (visited[index])
80
+ return;
81
+ const neighbors = regionQuery(index);
82
+ if (neighbors.length >= minPoints) {
83
+ const clusteredId = nextClusteredId;
84
+ nextClusteredId++;
85
+ visited[index] = true;
86
+ expandCluster(clusteredId, neighbors);
87
+ } else {
88
+ isnoise[index] = true;
89
+ }
90
+ });
91
+ points.features.forEach((_, index) => {
92
+ var clusterPoint = points.features[index];
93
+ if (!clusterPoint.properties) {
94
+ clusterPoint.properties = {};
95
+ }
96
+ if (clusterIds[index] >= 0) {
97
+ clusterPoint.properties.dbscan = isnoise[index] ? "edge" : "core";
98
+ clusterPoint.properties.cluster = clusterIds[index];
99
+ } else {
100
+ clusterPoint.properties.dbscan = "noise";
101
+ }
102
+ });
103
+ return points;
104
+ }
105
+ __name(clustersDbscan, "clustersDbscan");
106
+ var turf_clusters_dbscan_default = clustersDbscan;
107
+
108
+
109
+
110
+ exports.clustersDbscan = clustersDbscan; exports.default = turf_clusters_dbscan_default;
111
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../index.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\";\n// @ts-expect-error No types available for rbush.\nimport RBush from \"rbush\";\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 only)\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"]}
@@ -1,7 +1,8 @@
1
- import { GeoJsonProperties, FeatureCollection, Point } from "geojson";
2
- import { Units } from "@turf/helpers";
3
- export declare type Dbscan = "core" | "edge" | "noise";
4
- export declare type DbscanProps = GeoJsonProperties & {
1
+ import { GeoJsonProperties, FeatureCollection, Point } from 'geojson';
2
+ import { Units } from '@turf/helpers';
3
+
4
+ type Dbscan = "core" | "edge" | "noise";
5
+ type DbscanProps = GeoJsonProperties & {
5
6
  dbscan?: Dbscan;
6
7
  cluster?: number;
7
8
  };
@@ -33,4 +34,5 @@ declare function clustersDbscan(points: FeatureCollection<Point>, maxDistance: n
33
34
  minPoints?: number;
34
35
  mutate?: boolean;
35
36
  }): FeatureCollection<Point, DbscanProps>;
36
- export default clustersDbscan;
37
+
38
+ export { type Dbscan, type DbscanProps, clustersDbscan, clustersDbscan as default };
@@ -0,0 +1,38 @@
1
+ import { GeoJsonProperties, FeatureCollection, Point } from 'geojson';
2
+ import { Units } from '@turf/helpers';
3
+
4
+ type Dbscan = "core" | "edge" | "noise";
5
+ type DbscanProps = GeoJsonProperties & {
6
+ dbscan?: Dbscan;
7
+ cluster?: number;
8
+ };
9
+ /**
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.
11
+ *
12
+ * @name clustersDbscan
13
+ * @param {FeatureCollection<Point>} points to be clustered
14
+ * @param {number} maxDistance Maximum Distance between any point of the cluster to generate the clusters (kilometers only)
15
+ * @param {Object} [options={}] Optional parameters
16
+ * @param {string} [options.units="kilometers"] in which `maxDistance` is expressed, can be degrees, radians, miles, or kilometers
17
+ * @param {boolean} [options.mutate=false] Allows GeoJSON input to be mutated
18
+ * @param {number} [options.minPoints=3] Minimum number of points to generate a single cluster,
19
+ * 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:
21
+ * - {number} cluster - the associated clusterId
22
+ * - {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')
23
+ * @example
24
+ * // create random points with random z-values in their properties
25
+ * var points = turf.randomPoint(100, {bbox: [0, 30, 20, 50]});
26
+ * var maxDistance = 100;
27
+ * var clustered = turf.clustersDbscan(points, maxDistance);
28
+ *
29
+ * //addToMap
30
+ * var addToMap = [clustered];
31
+ */
32
+ declare function clustersDbscan(points: FeatureCollection<Point>, maxDistance: number, options?: {
33
+ units?: Units;
34
+ minPoints?: number;
35
+ mutate?: boolean;
36
+ }): FeatureCollection<Point, DbscanProps>;
37
+
38
+ export { type Dbscan, type DbscanProps, clustersDbscan, clustersDbscan as default };
@@ -0,0 +1,111 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // index.ts
5
+ import { clone } from "@turf/clone";
6
+ import { distance } from "@turf/distance";
7
+ import { degreesToRadians, lengthToDegrees } from "@turf/helpers";
8
+ import RBush from "rbush";
9
+ function clustersDbscan(points, maxDistance, options = {}) {
10
+ if (options.mutate !== true)
11
+ points = clone(points);
12
+ const minPoints = options.minPoints || 3;
13
+ const latDistanceInDegrees = lengthToDegrees(maxDistance, options.units);
14
+ var tree = new RBush(points.features.length);
15
+ var visited = points.features.map((_) => false);
16
+ var assigned = points.features.map((_) => false);
17
+ var isnoise = points.features.map((_) => false);
18
+ var clusterIds = points.features.map((_) => -1);
19
+ tree.load(
20
+ points.features.map((point, index) => {
21
+ var [x, y] = point.geometry.coordinates;
22
+ return {
23
+ minX: x,
24
+ minY: y,
25
+ maxX: x,
26
+ maxY: y,
27
+ index
28
+ };
29
+ })
30
+ );
31
+ const regionQuery = /* @__PURE__ */ __name((index) => {
32
+ const point = points.features[index];
33
+ const [x, y] = point.geometry.coordinates;
34
+ const minY = Math.max(y - latDistanceInDegrees, -90);
35
+ const maxY = Math.min(y + latDistanceInDegrees, 90);
36
+ const lonDistanceInDegrees = function() {
37
+ if (minY < 0 && maxY > 0) {
38
+ return latDistanceInDegrees;
39
+ }
40
+ if (Math.abs(minY) < Math.abs(maxY)) {
41
+ return latDistanceInDegrees / Math.cos(degreesToRadians(maxY));
42
+ } else {
43
+ return latDistanceInDegrees / Math.cos(degreesToRadians(minY));
44
+ }
45
+ }();
46
+ const minX = Math.max(x - lonDistanceInDegrees, -360);
47
+ const maxX = Math.min(x + lonDistanceInDegrees, 360);
48
+ const bbox = { minX, minY, maxX, maxY };
49
+ return tree.search(bbox).filter(
50
+ (neighbor) => {
51
+ const neighborIndex = neighbor.index;
52
+ const neighborPoint = points.features[neighborIndex];
53
+ const distanceInKm = distance(point, neighborPoint, {
54
+ units: "kilometers"
55
+ });
56
+ return distanceInKm <= maxDistance;
57
+ }
58
+ );
59
+ }, "regionQuery");
60
+ const expandCluster = /* @__PURE__ */ __name((clusteredId, neighbors) => {
61
+ for (var i = 0; i < neighbors.length; i++) {
62
+ var neighbor = neighbors[i];
63
+ const neighborIndex = neighbor.index;
64
+ if (!visited[neighborIndex]) {
65
+ visited[neighborIndex] = true;
66
+ const nextNeighbors = regionQuery(neighborIndex);
67
+ if (nextNeighbors.length >= minPoints) {
68
+ neighbors.push(...nextNeighbors);
69
+ }
70
+ }
71
+ if (!assigned[neighborIndex]) {
72
+ assigned[neighborIndex] = true;
73
+ clusterIds[neighborIndex] = clusteredId;
74
+ }
75
+ }
76
+ }, "expandCluster");
77
+ var nextClusteredId = 0;
78
+ points.features.forEach((_, index) => {
79
+ if (visited[index])
80
+ return;
81
+ const neighbors = regionQuery(index);
82
+ if (neighbors.length >= minPoints) {
83
+ const clusteredId = nextClusteredId;
84
+ nextClusteredId++;
85
+ visited[index] = true;
86
+ expandCluster(clusteredId, neighbors);
87
+ } else {
88
+ isnoise[index] = true;
89
+ }
90
+ });
91
+ points.features.forEach((_, index) => {
92
+ var clusterPoint = points.features[index];
93
+ if (!clusterPoint.properties) {
94
+ clusterPoint.properties = {};
95
+ }
96
+ if (clusterIds[index] >= 0) {
97
+ clusterPoint.properties.dbscan = isnoise[index] ? "edge" : "core";
98
+ clusterPoint.properties.cluster = clusterIds[index];
99
+ } else {
100
+ clusterPoint.properties.dbscan = "noise";
101
+ }
102
+ });
103
+ return points;
104
+ }
105
+ __name(clustersDbscan, "clustersDbscan");
106
+ var turf_clusters_dbscan_default = clustersDbscan;
107
+ export {
108
+ clustersDbscan,
109
+ turf_clusters_dbscan_default as default
110
+ };
111
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
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\";\n// @ts-expect-error No types available for rbush.\nimport RBush from \"rbush\";\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 only)\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"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turf/clusters-dbscan",
3
- "version": "7.0.0-alpha.1",
3
+ "version": "7.0.0-alpha.110+1411d63a7",
4
4
  "description": "turf clusters-dbscan module",
5
5
  "author": "Turf Authors",
6
6
  "contributors": [
@@ -30,54 +30,58 @@
30
30
  "density",
31
31
  "dbscan"
32
32
  ],
33
- "main": "dist/js/index.js",
34
- "module": "dist/es/index.js",
33
+ "type": "commonjs",
34
+ "main": "dist/cjs/index.cjs",
35
+ "module": "dist/esm/index.mjs",
36
+ "types": "dist/cjs/index.d.ts",
35
37
  "exports": {
36
38
  "./package.json": "./package.json",
37
39
  ".": {
38
- "types": "./dist/js/index.d.ts",
39
- "import": "./dist/es/index.js",
40
- "require": "./dist/js/index.js"
40
+ "import": {
41
+ "types": "./dist/esm/index.d.mts",
42
+ "default": "./dist/esm/index.mjs"
43
+ },
44
+ "require": {
45
+ "types": "./dist/cjs/index.d.ts",
46
+ "default": "./dist/cjs/index.cjs"
47
+ }
41
48
  }
42
49
  },
43
- "types": "dist/js/index.d.ts",
44
50
  "sideEffects": false,
45
51
  "files": [
46
52
  "dist"
47
53
  ],
48
54
  "scripts": {
49
- "bench": "tsx bench.js",
50
- "build": "npm-run-all build:*",
51
- "build:es": "tsc --outDir dist/es --module esnext --declaration false && echo '{\"type\":\"module\"}' > dist/es/package.json",
52
- "build:js": "tsc",
53
- "docs": "tsx ../../scripts/generate-readmes",
54
- "test": "npm-run-all test:*",
55
- "test:tape": "tsx test.js",
55
+ "bench": "tsx bench.ts",
56
+ "build": "tsup --config ../../tsup.config.ts",
57
+ "docs": "tsx ../../scripts/generate-readmes.ts",
58
+ "test": "npm-run-all --npm-path npm test:*",
59
+ "test:tape": "tsx test.ts",
56
60
  "test:types": "tsc --esModuleInterop --noEmit --strict types.ts"
57
61
  },
58
62
  "devDependencies": {
59
- "@turf/centroid": "^7.0.0-alpha.1",
60
- "@turf/clusters": "^7.0.0-alpha.1",
61
- "@types/density-clustering": "^1.3.0",
62
- "@types/tape": "*",
63
- "benchmark": "*",
64
- "chromatism": "*",
63
+ "@turf/centroid": "^7.0.0-alpha.110+1411d63a7",
64
+ "@turf/clusters": "^7.0.0-alpha.110+1411d63a7",
65
+ "@types/benchmark": "^2.1.5",
66
+ "@types/tape": "^4.2.32",
67
+ "benchmark": "^2.1.4",
68
+ "chromatism": "^3.0.0",
65
69
  "concaveman": "^1.2.1",
66
- "load-json-file": "*",
67
- "npm-run-all": "*",
68
- "tape": "*",
69
- "tslint": "*",
70
- "tsx": "*",
71
- "typescript": "*",
72
- "write-json-file": "*"
70
+ "load-json-file": "^7.0.1",
71
+ "npm-run-all": "^4.1.5",
72
+ "tape": "^5.7.2",
73
+ "tsup": "^8.0.1",
74
+ "tsx": "^4.6.2",
75
+ "typescript": "^5.2.2",
76
+ "write-json-file": "^5.0.0"
73
77
  },
74
78
  "dependencies": {
75
- "@turf/clone": "^7.0.0-alpha.1",
76
- "@turf/distance": "^7.0.0-alpha.1",
77
- "@turf/helpers": "^7.0.0-alpha.1",
78
- "@turf/meta": "^7.0.0-alpha.1",
79
- "density-clustering": "1.3.0",
80
- "tslib": "^2.3.0"
79
+ "@turf/clone": "^7.0.0-alpha.110+1411d63a7",
80
+ "@turf/distance": "^7.0.0-alpha.110+1411d63a7",
81
+ "@turf/helpers": "^7.0.0-alpha.110+1411d63a7",
82
+ "@turf/meta": "^7.0.0-alpha.110+1411d63a7",
83
+ "rbush": "^3.0.1",
84
+ "tslib": "^2.6.2"
81
85
  },
82
- "gitHead": "cf7a0c507b017ca066acffd0ce23bda5b393fb5a"
86
+ "gitHead": "1411d63a74c275c9216fe48e9d3cb2d48a359068"
83
87
  }
package/dist/es/index.js DELETED
@@ -1,69 +0,0 @@
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
- // Clone points to prevent any mutations
36
- if (options.mutate !== true)
37
- points = clone(points);
38
- // Defaults
39
- options.minPoints = options.minPoints || 3;
40
- // create clustered ids
41
- var dbscan = new clustering.DBSCAN();
42
- var clusteredIds = dbscan.run(coordAll(points), convertLength(maxDistance, options.units), options.minPoints, distance);
43
- // Tag points to Clusters ID
44
- var clusterId = -1;
45
- clusteredIds.forEach(function (clusterIds) {
46
- clusterId++;
47
- // assign cluster ids to input points
48
- clusterIds.forEach(function (idx) {
49
- var clusterPoint = points.features[idx];
50
- if (!clusterPoint.properties)
51
- clusterPoint.properties = {};
52
- clusterPoint.properties.cluster = clusterId;
53
- clusterPoint.properties.dbscan = "core";
54
- });
55
- });
56
- // handle noise points, if any
57
- // edges points are tagged by DBSCAN as both 'noise' and 'cluster' as they can "reach" less than 'minPoints' number of points
58
- dbscan.noise.forEach(function (noiseId) {
59
- var noisePoint = points.features[noiseId];
60
- if (!noisePoint.properties)
61
- noisePoint.properties = {};
62
- if (noisePoint.properties.cluster)
63
- noisePoint.properties.dbscan = "edge";
64
- else
65
- noisePoint.properties.dbscan = "noise";
66
- });
67
- return points;
68
- }
69
- export default clustersDbscan;
@@ -1 +0,0 @@
1
- {"type":"module"}
package/dist/js/index.js DELETED
@@ -1,72 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- const clone_1 = tslib_1.__importDefault(require("@turf/clone"));
5
- const distance_1 = tslib_1.__importDefault(require("@turf/distance"));
6
- const meta_1 = require("@turf/meta");
7
- const helpers_1 = require("@turf/helpers");
8
- const density_clustering_1 = tslib_1.__importDefault(require("density-clustering"));
9
- /**
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.
11
- *
12
- * @name clustersDbscan
13
- * @param {FeatureCollection<Point>} points to be clustered
14
- * @param {number} maxDistance Maximum Distance between any point of the cluster to generate the clusters (kilometers only)
15
- * @param {Object} [options={}] Optional parameters
16
- * @param {string} [options.units="kilometers"] in which `maxDistance` is expressed, can be degrees, radians, miles, or kilometers
17
- * @param {boolean} [options.mutate=false] Allows GeoJSON input to be mutated
18
- * @param {number} [options.minPoints=3] Minimum number of points to generate a single cluster,
19
- * 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:
21
- * - {number} cluster - the associated clusterId
22
- * - {string} dbscan - type of point it has been classified as ('core'|'edge'|'noise')
23
- * @example
24
- * // create random points with random z-values in their properties
25
- * var points = turf.randomPoint(100, {bbox: [0, 30, 20, 50]});
26
- * var maxDistance = 100;
27
- * var clustered = turf.clustersDbscan(points, maxDistance);
28
- *
29
- * //addToMap
30
- * var addToMap = [clustered];
31
- */
32
- function clustersDbscan(points, maxDistance, options = {}) {
33
- // Input validation being handled by Typescript
34
- // collectionOf(points, 'Point', 'points must consist of a FeatureCollection of only Points');
35
- // if (maxDistance === null || maxDistance === undefined) throw new Error('maxDistance is required');
36
- // if (!(Math.sign(maxDistance) > 0)) throw new Error('maxDistance is invalid');
37
- // if (!(minPoints === undefined || minPoints === null || Math.sign(minPoints) > 0)) throw new Error('options.minPoints is invalid');
38
- // Clone points to prevent any mutations
39
- if (options.mutate !== true)
40
- points = clone_1.default(points);
41
- // Defaults
42
- options.minPoints = options.minPoints || 3;
43
- // create clustered ids
44
- var dbscan = new density_clustering_1.default.DBSCAN();
45
- var clusteredIds = dbscan.run(meta_1.coordAll(points), helpers_1.convertLength(maxDistance, options.units), options.minPoints, distance_1.default);
46
- // Tag points to Clusters ID
47
- var clusterId = -1;
48
- clusteredIds.forEach(function (clusterIds) {
49
- clusterId++;
50
- // assign cluster ids to input points
51
- clusterIds.forEach(function (idx) {
52
- var clusterPoint = points.features[idx];
53
- if (!clusterPoint.properties)
54
- clusterPoint.properties = {};
55
- clusterPoint.properties.cluster = clusterId;
56
- clusterPoint.properties.dbscan = "core";
57
- });
58
- });
59
- // handle noise points, if any
60
- // edges points are tagged by DBSCAN as both 'noise' and 'cluster' as they can "reach" less than 'minPoints' number of points
61
- dbscan.noise.forEach(function (noiseId) {
62
- var noisePoint = points.features[noiseId];
63
- if (!noisePoint.properties)
64
- noisePoint.properties = {};
65
- if (noisePoint.properties.cluster)
66
- noisePoint.properties.dbscan = "edge";
67
- else
68
- noisePoint.properties.dbscan = "noise";
69
- });
70
- return points;
71
- }
72
- exports.default = clustersDbscan;