From 5039b0dbf3af3b93466069927d794f5b1c8ccf81 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Tue, 4 May 2021 14:50:52 +1000 Subject: add cluster library --- cluster.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 cluster.js (limited to 'cluster.js') diff --git a/cluster.js b/cluster.js new file mode 100644 index 0000000..c716063 --- /dev/null +++ b/cluster.js @@ -0,0 +1,64 @@ +const CheapRuler = require('cheap-ruler') +const ruler = new CheapRuler(-37, 'meters') + +/** + * Cluster points together where within threshold distance. + * + * @param {Array} features - GeoJSON Point Features + * @param {number} thresholdDistance - Maximum distance between points to cluster together + * + * @returns {Array} clusters, where unclustered features are returned as single feature clusters + */ +module.exports = (features, thresholdDistance) => { + // Array of clusters where each cluster is a Set of feature index's + const clusters = [] + + features.map((a, ai) => { + features.map((b, bi) => { + // skip comparing with self + if (ai === bi) return + + const distance = ruler.distance(a.geometry.coordinates, b.geometry.coordinates) + if (distance < thresholdDistance) { + // link into a cluster + let addedToExistingCluster = false + clusters.forEach((cluster, i) => { + if (cluster.has(ai) || cluster.has(bi)) { + // insert into this cluster + clusters[i].add(ai) + clusters[i].add(bi) + + addedToExistingCluster = true + } + }) + + if (!addedToExistingCluster) { + // create a new cluster + const newCluster = new Set() + newCluster.add(ai) + newCluster.add(bi) + clusters.push(newCluster) + } + } // else don't cluster together + }) + }) + + // result is array of clusters, including non-clustered features as single item clusters + const result = clusters.map(cluster => { + return Array.from(cluster).map(index => { + return features[index] + }) + }) + + // find features not clustered + features.map((feature, index) => { + // if feature not a cluster, return as an single item cluster + const featureInACluster = clusters.map(cluster => cluster.has(index)).reduce((acc, cur) => acc || !!cur, false) + if (!featureInACluster) { + result.push([feature]) + } + }) + + return result + +} -- cgit v1.2.3