diff --git a/packages/turf-clusters/LICENSE b/packages/turf-clusters/LICENSE
new file mode 100644
index 0000000000..96ce51b76f
--- /dev/null
+++ b/packages/turf-clusters/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 TurfJS
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/turf-clusters/README.md b/packages/turf-clusters/README.md
new file mode 100644
index 0000000000..0bff2c933d
--- /dev/null
+++ b/packages/turf-clusters/README.md
@@ -0,0 +1,201 @@
+# @turf/clusters
+
+# getCluster
+
+Get Cluster
+
+**Parameters**
+
+- `geojson` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)** GeoJSON Features
+- `filter` **Any** Filter used on GeoJSON properties to get Cluster
+
+**Examples**
+
+```javascript
+var geojson = turf.featureCollection([
+ turf.point([0, 0], {'marker-symbol': 'circle'}),
+ turf.point([2, 4], {'marker-symbol': 'star'}),
+ turf.point([3, 6], {'marker-symbol': 'star'}),
+ turf.point([5, 1], {'marker-symbol': 'square'}),
+ turf.point([4, 2], {'marker-symbol': 'circle'})
+]);
+
+// Create a cluster using K-Means (adds `cluster` to GeoJSON properties)
+var clustered = turf.clustersKmeans(geojson);
+
+// Retrieve first cluster (0)
+var cluster = turf.getCluster(clustered, {cluster: 0});
+//= cluster
+
+// Retrieve cluster based on custom properties
+turf.getCluster(clustered, {'marker-symbol': 'circle'}).length;
+//= 2
+turf.getCluster(clustered, {'marker-symbol': 'square'}).length;
+//= 1
+```
+
+Returns **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)** Single Cluster filtered by GeoJSON Properties
+
+# clusterEachCallback
+
+Callback for clusterEach
+
+**Parameters**
+
+- `cluster` **\[[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)]** The current cluster being processed.
+- `clusterValue` **\[Any]** Value used to create cluster being processed.
+- `currentIndex` **\[[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** The index of the current element being processed in the array.Starts at index 0
+- `geojson`
+- `property`
+- `callback`
+
+Returns **void**
+
+# clusterEach
+
+clusterEach
+
+**Parameters**
+
+- `geojson` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)** GeoJSON Features
+- `property` **([string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number))** GeoJSON property key/value used to create clusters
+- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** a method that takes (cluster, clusterValue, currentIndex)
+
+**Examples**
+
+```javascript
+var geojson = turf.featureCollection([
+ turf.point([0, 0]),
+ turf.point([2, 4]),
+ turf.point([3, 6]),
+ turf.point([5, 1]),
+ turf.point([4, 2])
+]);
+
+// Create a cluster using K-Means (adds `cluster` to GeoJSON properties)
+var clustered = turf.clustersKmeans(geojson);
+
+// Iterate over each cluster
+clusterEach(clustered, 'cluster', function (cluster, clusterValue, currentIndex) {
+ //= cluster
+ //= clusterValue
+ //= currentIndex
+})
+
+// Calculate the total number of clusters
+var total = 0
+turf.clusterEach(clustered, 'cluster', function () {
+ total++;
+});
+
+// Create an Array of all the values retrieved from the 'cluster' property
+var values = []
+turf.clusterEach(clustered, 'cluster', function (cluster, clusterValue) {
+ values.push(clusterValue);
+});
+```
+
+Returns **void**
+
+# clusterReduceCallback
+
+Callback for clusterReduce
+
+The first time the callback function is called, the values provided as arguments depend
+on whether the reduce method has an initialValue argument.
+
+If an initialValue is provided to the reduce method:
+
+- The previousValue argument is initialValue.
+- The currentValue argument is the value of the first element present in the array.
+
+If an initialValue is not provided:
+
+- The previousValue argument is the value of the first element present in the array.
+- The currentValue argument is the value of the second element present in the array.
+
+**Parameters**
+
+- `previousValue` **\[Any]** The accumulated value previously returned in the last invocation
+ of the callback, or initialValue, if supplied.
+- `cluster` **\[[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)]** The current cluster being processed.
+- `clusterValue` **\[Any]** Value used to create cluster being processed.
+- `currentIndex` **\[[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** The index of the current element being processed in the
+ array. Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
+- `geojson`
+- `property`
+- `callback`
+- `initialValue`
+
+# clusterReduce
+
+Reduce clusters in GeoJSON Features, similar to Array.reduce()
+
+**Parameters**
+
+- `geojson` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)** GeoJSON Features
+- `property` **([string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number))** GeoJSON property key/value used to create clusters
+- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** a method that takes (previousValue, cluster, clusterValue, currentIndex)
+- `initialValue` **\[Any]** Value to use as the first argument to the first call of the callback.
+
+**Examples**
+
+```javascript
+var geojson = turf.featureCollection([
+ turf.point([0, 0]),
+ turf.point([2, 4]),
+ turf.point([3, 6]),
+ turf.point([5, 1]),
+ turf.point([4, 2])
+]);
+
+// Create a cluster using K-Means (adds `cluster` to GeoJSON properties)
+var clustered = turf.clustersKmeans(geojson);
+
+// Iterate over each cluster and perform a calculation
+var initialValue = 0
+turf.clusterReduce(clustered, 'cluster', function (previousValue, cluster, clusterValue, currentIndex) {
+ //=previousValue
+ //=cluster
+ //=clusterValue
+ //=currentIndex
+ return previousValue++;
+}, initialValue);
+
+// Calculate the total number of clusters
+var total = turf.clusterReduce(clustered, 'cluster', function (previousValue) {
+ return previousValue++;
+}, 0);
+
+// Create an Array of all the values retrieved from the 'cluster' property
+var values = turf.clusterReduce(clustered, 'cluster', function (previousValue, cluster, clusterValue) {
+ return previousValue.push(clusterValue);
+}, []);
+```
+
+Returns **Any** The value that results from the reduction.
+
+
+
+---
+
+This module is part of the [Turfjs project](http://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.
+
+### Installation
+
+Install this module individually:
+
+```sh
+$ npm install @turf/clusters
+```
+
+Or install the Turf module that includes it as a function:
+
+```sh
+$ npm install @turf/turf
+```
diff --git a/packages/turf-clusters/bench.js b/packages/turf-clusters/bench.js
new file mode 100644
index 0000000000..eef6417162
--- /dev/null
+++ b/packages/turf-clusters/bench.js
@@ -0,0 +1,46 @@
+const Benchmark = require('benchmark');
+const {featureCollection, point} = require('@turf/helpers');
+const {getCluster, clusterEach, clusterReduce} = require('./');
+const {propertiesContainsFilter, filterProperties, applyFilter, createBins} = require('./'); // Testing Purposes
+
+const geojson = featureCollection([
+ point([0, 0], {cluster: 0}),
+ point([2, 4], {cluster: 1}),
+ point([3, 6], {cluster: 1}),
+ point([5, 1], {0: 'foo'}),
+ point([4, 2], {'bar': 'foo'}),
+ point([2, 4], {}),
+ point([4, 3], undefined)
+]);
+
+/**
+ * Benchmark Results
+ *
+ * testing -- createBins x 1,909,730 ops/sec ±2.70% (83 runs sampled)
+ * testing -- propertiesContainsFilter x 10,378,738 ops/sec ±2.63% (86 runs sampled)
+ * testing -- filterProperties x 26,212,665 ops/sec ±2.49% (85 runs sampled)
+ * testing -- applyFilter x 21,368,185 ops/sec ±2.71% (84 runs sampled)
+ * getCluster -- string filter x 3,051,513 ops/sec ±1.83% (84 runs sampled)
+ * getCluster -- object filter x 673,824 ops/sec ±2.20% (86 runs sampled)
+ * getCluster -- aray filter x 2,284,972 ops/sec ±1.90% (86 runs sampled)
+ * clusterEach x 890,683 ops/sec ±1.48% (87 runs sampled)
+ * clusterReduce x 837,383 ops/sec ±1.93% (87 runs sampled)
+ */
+const suite = new Benchmark.Suite('turf-clusters');
+
+// Testing Purposes
+suite
+ .add('testing -- createBins', () => createBins(geojson, 'cluster'))
+ .add('testing -- propertiesContainsFilter', () => propertiesContainsFilter({foo: 'bar', cluster: 0}, {cluster: 0}))
+ .add('testing -- filterProperties', () => filterProperties({foo: 'bar', cluster: 0}, ['cluster']))
+ .add('testing -- applyFilter', () => applyFilter({foo: 'bar', cluster: 0}, ['cluster']));
+
+suite
+ .add('getCluster -- string filter', () => getCluster(geojson, 'cluster'))
+ .add('getCluster -- object filter', () => getCluster(geojson, {cluster: 1}))
+ .add('getCluster -- aray filter', () => getCluster(geojson, ['cluster']))
+ .add('clusterEach', () => clusterEach(geojson, 'cluster', cluster => { return cluster; }))
+ .add('clusterReduce', () => clusterReduce(geojson, 'cluster', (previousValue, cluster) => { return cluster; }))
+ .on('cycle', e => console.log(String(e.target)))
+ .on('complete', () => {})
+ .run();
diff --git a/packages/turf-clusters/index.d.ts b/packages/turf-clusters/index.d.ts
new file mode 100644
index 0000000000..0a4e28c208
--- /dev/null
+++ b/packages/turf-clusters/index.d.ts
@@ -0,0 +1,32 @@
+///
+
+type FeatureCollection = GeoJSON.FeatureCollection;
+type Feature = GeoJSON.Feature;
+type GeometryObject = GeoJSON.GeometryObject;
+
+/**
+ * http://turfjs.org/docs/#getcluster
+ */
+export function getCluster(
+ geojson: FeatureCollection,
+ filter: any
+): FeatureCollection;
+
+/**
+ * http://turfjs.org/docs/#clustereach
+ */
+export function clusterEach(
+ geojson: FeatureCollection,
+ property: number | string,
+ callback: (cluster?: FeatureCollection, clusterValue?: any, currentIndex?: number) => void
+): void;
+
+/**
+ * http://turfjs.org/docs/#clusterreduce
+ */
+export function clusterReduce(
+ geojson: FeatureCollection,
+ property: number | string,
+ callback: (previousValue?: any, cluster?: FeatureCollection, clusterValue?: any, currentIndex?: number) => void,
+ initialValue: any
+): void;
diff --git a/packages/turf-clusters/index.js b/packages/turf-clusters/index.js
new file mode 100644
index 0000000000..66dd1b0fab
--- /dev/null
+++ b/packages/turf-clusters/index.js
@@ -0,0 +1,297 @@
+var featureEach = require('@turf/meta').featureEach;
+var featureCollection = require('@turf/helpers').featureCollection;
+
+/**
+ * Get Cluster
+ *
+ * @param {FeatureCollection} geojson GeoJSON Features
+ * @param {*} filter Filter used on GeoJSON properties to get Cluster
+ * @returns {FeatureCollection} Single Cluster filtered by GeoJSON Properties
+ * @example
+ * var geojson = turf.featureCollection([
+ * turf.point([0, 0], {'marker-symbol': 'circle'}),
+ * turf.point([2, 4], {'marker-symbol': 'star'}),
+ * turf.point([3, 6], {'marker-symbol': 'star'}),
+ * turf.point([5, 1], {'marker-symbol': 'square'}),
+ * turf.point([4, 2], {'marker-symbol': 'circle'})
+ * ]);
+ *
+ * // Create a cluster using K-Means (adds `cluster` to GeoJSON properties)
+ * var clustered = turf.clustersKmeans(geojson);
+ *
+ * // Retrieve first cluster (0)
+ * var cluster = turf.getCluster(clustered, {cluster: 0});
+ * //= cluster
+ *
+ * // Retrieve cluster based on custom properties
+ * turf.getCluster(clustered, {'marker-symbol': 'circle'}).length;
+ * //= 2
+ * turf.getCluster(clustered, {'marker-symbol': 'square'}).length;
+ * //= 1
+ */
+function getCluster(geojson, filter) {
+ // Validation
+ if (!geojson) throw new Error('geojson is required');
+ if (geojson.type !== 'FeatureCollection') throw new Error('geojson must be a FeatureCollection');
+ if (filter === undefined || filter === null) throw new Error('filter is required');
+
+ // Filter Features
+ var features = [];
+ featureEach(geojson, function (feature) {
+ if (applyFilter(feature.properties, filter)) features.push(feature);
+ });
+ return featureCollection(features);
+}
+
+/**
+ * Callback for clusterEach
+ *
+ * @callback clusterEachCallback
+ * @param {FeatureCollection} [cluster] The current cluster being processed.
+ * @param {*} [clusterValue] Value used to create cluster being processed.
+ * @param {number} [currentIndex] The index of the current element being processed in the array.Starts at index 0
+ * @returns {void}
+ */
+
+/**
+ * clusterEach
+ *
+ * @param {FeatureCollection} geojson GeoJSON Features
+ * @param {string|number} property GeoJSON property key/value used to create clusters
+ * @param {Function} callback a method that takes (cluster, clusterValue, currentIndex)
+ * @returns {void}
+ * @example
+ * var geojson = turf.featureCollection([
+ * turf.point([0, 0]),
+ * turf.point([2, 4]),
+ * turf.point([3, 6]),
+ * turf.point([5, 1]),
+ * turf.point([4, 2])
+ * ]);
+ *
+ * // Create a cluster using K-Means (adds `cluster` to GeoJSON properties)
+ * var clustered = turf.clustersKmeans(geojson);
+ *
+ * // Iterate over each cluster
+ * clusterEach(clustered, 'cluster', function (cluster, clusterValue, currentIndex) {
+ * //= cluster
+ * //= clusterValue
+ * //= currentIndex
+ * })
+ *
+ * // Calculate the total number of clusters
+ * var total = 0
+ * turf.clusterEach(clustered, 'cluster', function () {
+ * total++;
+ * });
+ *
+ * // Create an Array of all the values retrieved from the 'cluster' property
+ * var values = []
+ * turf.clusterEach(clustered, 'cluster', function (cluster, clusterValue) {
+ * values.push(clusterValue);
+ * });
+ */
+function clusterEach(geojson, property, callback) {
+ // Validation
+ if (!geojson) throw new Error('geojson is required');
+ if (geojson.type !== 'FeatureCollection') throw new Error('geojson must be a FeatureCollection');
+ if (property === undefined || property === null) throw new Error('property is required');
+
+ // Create clusters based on property values
+ var bins = createBins(geojson, property);
+ var values = Object.keys(bins);
+ for (var index = 0; index < values.length; index++) {
+ var value = values[index];
+ var bin = bins[value];
+ var features = [];
+ for (var i = 0; i < bin.length; i++) {
+ features.push(geojson[bin[i]]);
+ }
+ callback(featureCollection(features), value, index);
+ }
+}
+
+/**
+ * Callback for clusterReduce
+ *
+ * The first time the callback function is called, the values provided as arguments depend
+ * on whether the reduce method has an initialValue argument.
+ *
+ * If an initialValue is provided to the reduce method:
+ * - The previousValue argument is initialValue.
+ * - The currentValue argument is the value of the first element present in the array.
+ *
+ * If an initialValue is not provided:
+ * - The previousValue argument is the value of the first element present in the array.
+ * - The currentValue argument is the value of the second element present in the array.
+ *
+ * @callback clusterReduceCallback
+ * @param {*} [previousValue] The accumulated value previously returned in the last invocation
+ * of the callback, or initialValue, if supplied.
+ * @param {FeatureCollection} [cluster] The current cluster being processed.
+ * @param {*} [clusterValue] Value used to create cluster being processed.
+ * @param {number} [currentIndex] The index of the current element being processed in the
+ * array. Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
+ */
+
+/**
+ * Reduce clusters in GeoJSON Features, similar to Array.reduce()
+ *
+ * @name clusterReduce
+ * @param {FeatureCollection} geojson GeoJSON Features
+ * @param {string|number} property GeoJSON property key/value used to create clusters
+ * @param {Function} callback a method that takes (previousValue, cluster, clusterValue, currentIndex)
+ * @param {*} [initialValue] Value to use as the first argument to the first call of the callback.
+ * @returns {*} The value that results from the reduction.
+ * @example
+ * var geojson = turf.featureCollection([
+ * turf.point([0, 0]),
+ * turf.point([2, 4]),
+ * turf.point([3, 6]),
+ * turf.point([5, 1]),
+ * turf.point([4, 2])
+ * ]);
+ *
+ * // Create a cluster using K-Means (adds `cluster` to GeoJSON properties)
+ * var clustered = turf.clustersKmeans(geojson);
+ *
+ * // Iterate over each cluster and perform a calculation
+ * var initialValue = 0
+ * turf.clusterReduce(clustered, 'cluster', function (previousValue, cluster, clusterValue, currentIndex) {
+ * //=previousValue
+ * //=cluster
+ * //=clusterValue
+ * //=currentIndex
+ * return previousValue++;
+ * }, initialValue);
+ *
+ * // Calculate the total number of clusters
+ * var total = turf.clusterReduce(clustered, 'cluster', function (previousValue) {
+ * return previousValue++;
+ * }, 0);
+ *
+ * // Create an Array of all the values retrieved from the 'cluster' property
+ * var values = turf.clusterReduce(clustered, 'cluster', function (previousValue, cluster, clusterValue) {
+ * return previousValue.push(clusterValue);
+ * }, []);
+ */
+function clusterReduce(geojson, property, callback, initialValue) {
+ var previousValue = initialValue;
+ clusterEach(geojson, property, function (cluster, clusterValue, currentIndex) {
+ if (currentIndex === 0 && initialValue === undefined) previousValue = cluster;
+ else previousValue = callback(previousValue, cluster, clusterValue, currentIndex);
+ });
+ return previousValue;
+}
+
+/**
+ * Create Bins
+ *
+ * @private
+ * @param {FeatureCollection} geojson GeoJSON Features
+ * @param {string|number} property Property values are used to create bins
+ * @returns {Object} bins with Feature IDs
+ * @example
+ * const geojson = turf.featureCollection([
+ * turf.point([0, 0], {cluster: 0, foo: 'null'}),
+ * turf.point([2, 4], {cluster: 1, foo: 'bar'}),
+ * turf.point([5, 1], {0: 'foo'}),
+ * turf.point([3, 6], {cluster: 1}),
+ * ]);
+ * createBins(geojson, 'cluster');
+ * //= { '0': [ 0 ], '1': [ 1, 3 ] }
+ */
+function createBins(geojson, property) {
+ var bins = {};
+
+ featureEach(geojson, function (feature, i) {
+ var properties = feature.properties || {};
+ if (properties.hasOwnProperty(property)) {
+ var value = properties[property];
+ if (bins.hasOwnProperty(value)) bins[value].push(i);
+ else bins[value] = [i];
+ }
+ });
+ return bins;
+}
+
+/**
+ * Apply Filter
+ *
+ * @private
+ * @param {*} properties Properties
+ * @param {*} filter Filter
+ * @returns {Boolean} applied Filter to properties
+ */
+function applyFilter(properties, filter) {
+ if (properties === undefined) return false;
+ var filterType = typeof filter;
+
+ // String & Number
+ if (filterType === 'number' || filterType === 'string') return properties.hasOwnProperty(filter);
+ // Array
+ else if (Array.isArray(filter)) {
+ for (var i = 0; i < filter.length; i++) {
+ if (!applyFilter(properties, filter[i])) return false;
+ }
+ return true;
+ // Object
+ } else {
+ return propertiesContainsFilter(properties, filter);
+ }
+}
+
+/**
+ * Properties contains filter (does not apply deepEqual operations)
+ *
+ * @private
+ * @param {*} properties Properties
+ * @param {Object} filter Filter
+ * @returns {Boolean} does filter equal Properties
+ * @example
+ * propertiesContainsFilter({foo: 'bar', cluster: 0}, {cluster: 0})
+ * //= true
+ * propertiesContainsFilter({foo: 'bar', cluster: 0}, {cluster: 1})
+ * //= false
+ */
+function propertiesContainsFilter(properties, filter) {
+ var keys = Object.keys(filter);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ if (properties[key] !== filter[key]) return false;
+ }
+ return true;
+}
+
+/**
+ * Filter Properties
+ *
+ * @private
+ * @param {*} properties Properties
+ * @param {string[]} keys Used to filter Properties
+ * @returns {*} filtered Properties
+ * @example
+ * filterProperties({foo: 'bar', cluster: 0}, ['cluster'])
+ * //= {cluster: 0}
+ */
+function filterProperties(properties, keys) {
+ if (!keys) return {};
+ if (!keys.length) return {};
+
+ var newProperties = {};
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ if (properties.hasOwnProperty(key)) newProperties[key] = properties[key];
+ }
+ return newProperties;
+}
+
+module.exports = {
+ getCluster: getCluster,
+ clusterEach: clusterEach,
+ clusterReduce: clusterReduce,
+ createBins: createBins, // Not exposed in @turf/turf - Internal purposes only
+ applyFilter: applyFilter, // Not exposed in @turf/turf - Internal purposes only
+ propertiesContainsFilter: propertiesContainsFilter, // Not exposed in @turf/turf - Internal purposes only
+ filterProperties: filterProperties // Not exposed in @turf/turf - Internal purposes only
+};
diff --git a/packages/turf-clusters/package.json b/packages/turf-clusters/package.json
new file mode 100644
index 0000000000..8db7376ab8
--- /dev/null
+++ b/packages/turf-clusters/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@turf/clusters",
+ "version": "4.6.0",
+ "description": "turf clusters module",
+ "main": "index.js",
+ "types": "index.d.ts",
+ "files": [
+ "index.js",
+ "index.d.ts"
+ ],
+ "scripts": {
+ "test": "node test.js",
+ "bench": "node bench.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/Turfjs/turf.git"
+ },
+ "keywords": [
+ "turf",
+ "geojson",
+ "cluster",
+ "clusters",
+ "clustering"
+ ],
+ "author": "Turf Authors",
+ "contributors": [
+ "Denis Carriere <@DenisCarriere>"
+ ],
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/Turfjs/turf/issues"
+ },
+ "homepage": "https://github.com/Turfjs/turf",
+ "devDependencies": {
+ "benchmark": "^2.1.4",
+ "tape": "^4.6.3"
+ },
+ "dependencies": {
+ "@turf/helpers": "^4.5.2",
+ "@turf/meta": "^4.5.2"
+ }
+}
diff --git a/packages/turf-clusters/test.js b/packages/turf-clusters/test.js
new file mode 100644
index 0000000000..8c89f5bd31
--- /dev/null
+++ b/packages/turf-clusters/test.js
@@ -0,0 +1,83 @@
+const test = require('tape');
+const {featureCollection, point} = require('@turf/helpers');
+const {propertiesContainsFilter, filterProperties, applyFilter, createBins} = require('./'); // Testing Purposes
+const {getCluster, clusterEach, clusterReduce} = require('./');
+
+const properties = {foo: 'bar', cluster: 0};
+const geojson = featureCollection([
+ point([0, 0], {cluster: 0, foo: 'null'}),
+ point([2, 4], {cluster: 1, foo: 'bar'}),
+ point([3, 6], {cluster: 1}),
+ point([5, 1], {0: 'foo'}),
+ point([4, 2], {'bar': 'foo'}),
+ point([2, 4], {}),
+ point([4, 3], undefined)
+]);
+
+test('clusters -- getCluster', t => {
+ t.equal(getCluster(geojson, 0).features.length, 1, 'number1');
+ t.equal(getCluster(geojson, 1).features.length, 0, 'number2');
+ t.equal(getCluster(geojson, 'bar').features.length, 1, 'string1');
+ t.equal(getCluster(geojson, 'cluster').features.length, 3, 'string2');
+ t.equal(getCluster(geojson, {cluster: 1}).features.length, 2, 'object1');
+ t.equal(getCluster(geojson, {cluster: 0}).features.length, 1, 'object2');
+ t.equal(getCluster(geojson, ['cluster', {foo: 'bar'}]).features.length, 1);
+ t.equal(getCluster(geojson, ['cluster', 'foo']).features.length, 2);
+ t.equal(getCluster(geojson, ['cluster']).features.length, 3);
+ t.end();
+});
+
+test('clusters -- clusterEach', t => {
+ const clusters = [];
+ let total = 0;
+ clusterEach(geojson, 'cluster', (cluster) => {
+ total += cluster.features.length;
+ clusters.push(cluster);
+ });
+ t.equal(total, 3);
+ t.equal(clusters.length, 2);
+ t.end();
+});
+
+test('clusters -- clusterReduce', t => {
+ const clusters = [];
+ const total = clusterReduce(geojson, 'cluster', (previousValue, cluster) => {
+ clusters.push(cluster);
+ return previousValue + cluster.features.length;
+ }, 0);
+ t.equal(total, 3);
+ t.equal(clusters.length, 2);
+ t.end();
+});
+
+// Internal purposes only
+test('clusters -- applyFilter', t => {
+ t.true(applyFilter(properties, 'cluster'));
+ t.true(applyFilter(properties, ['cluster']));
+ t.false(applyFilter(properties, {cluster: 1}));
+ t.true(applyFilter(properties, {cluster: 0}));
+ t.false(applyFilter(undefined, {cluster: 0}));
+ t.end();
+});
+
+// Internal purposes only
+test('clusters -- filterProperties', t => {
+ t.deepEqual(filterProperties(properties, ['cluster']), {cluster: 0});
+ t.deepEqual(filterProperties(properties, []), {});
+ t.deepEqual(filterProperties(properties, undefined), {});
+ t.end();
+});
+
+// Internal purposes only
+test('clusters -- propertiesContainsFilter', t => {
+ t.deepEqual(propertiesContainsFilter(properties, {cluster: 0}), true);
+ t.deepEqual(propertiesContainsFilter(properties, {cluster: 1}), false);
+ t.deepEqual(propertiesContainsFilter(properties, {bar: 'foo'}), false);
+ t.end();
+});
+
+// Internal purposes only
+test('clusters -- propertiesContainsFilter', t => {
+ t.deepEqual(createBins(geojson, 'cluster'), {'0': [0], '1': [1, 2]});
+ t.end();
+});
diff --git a/packages/turf-clusters/types.ts b/packages/turf-clusters/types.ts
new file mode 100644
index 0000000000..ec17a67ce3
--- /dev/null
+++ b/packages/turf-clusters/types.ts
@@ -0,0 +1,64 @@
+import {featureCollection, point} from '@turf/helpers'
+import {getCluster, clusterEach, clusterReduce} from './'
+
+/**
+ * Fixtures
+ */
+const geojson = featureCollection([
+ point([0, 0], {cluster: 0}),
+ point([2, 4], {cluster: 1}),
+ point([3, 6], {cluster: 1}),
+ point([3, 6], {0: 'foo'}),
+ point([3, 6], {'bar': 'foo'})
+]);
+
+/**
+ * Get Cluster
+ */
+getCluster(geojson, {cluster: 1});
+getCluster(geojson, {0: 'foo'});
+getCluster(geojson, {'bar': 'foo'});
+getCluster(geojson, 'cluster');
+getCluster(geojson, ['cluster', 'bar']);
+getCluster(geojson, 0);
+
+/**
+ * ClusterEach
+ */
+clusterEach(geojson, 'cluster', (cluster, clusterValue, currentIndex) => {
+ //= cluster
+ //= clusterValue
+ //= currentIndex
+})
+// Calculate the total number of clusters
+let total = 0
+clusterEach(geojson, 'cluster', function () {
+ total++;
+});
+
+// Create an Array of all the values retrieved from the 'cluster' property
+const values: number[] = []
+clusterEach(geojson, 'cluster', function (cluster, clusterValue: number) {
+ values.push(clusterValue);
+});
+
+/**
+ * ClusterReduce
+ */
+const initialValue = 0;
+clusterReduce(geojson, 'cluster', (previousValue, cluster, clusterValue, currentIndex) => {
+ //= previousValue
+ //= cluster
+ //= clusterValue
+ //= currentIndex
+}, initialValue)
+
+// Calculate the total number of clusters
+const totalReduce = clusterReduce(geojson, 'cluster', function (previousValue) {
+ return previousValue++;
+}, 0);
+
+// Create an Array of all the values retrieved from the 'cluster' property
+const valuesReduce = clusterReduce(geojson, 'cluster', function (previousValue, cluster, clusterValue) {
+ return previousValue.push(clusterValue);
+}, []);
diff --git a/packages/turf-clusters/yarn.lock b/packages/turf-clusters/yarn.lock
new file mode 100644
index 0000000000..2c870434ef
--- /dev/null
+++ b/packages/turf-clusters/yarn.lock
@@ -0,0 +1,219 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@turf/helpers@^4.5.2":
+ version "4.5.2"
+ resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-4.5.2.tgz#6fc6772a7b301f277b49732925c354c7fb85d1df"
+
+"@turf/meta@^4.5.2":
+ version "4.5.2"
+ resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-4.5.2.tgz#8450fc442d2a59494251a5a52ae520017e2dcf0d"
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+
+benchmark@^2.1.4:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
+ dependencies:
+ lodash "^4.17.4"
+ platform "^1.3.3"
+
+brace-expansion@^1.1.7:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+
+deep-equal@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
+
+define-properties@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
+ dependencies:
+ foreach "^2.0.5"
+ object-keys "^1.0.8"
+
+defined@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+
+es-abstract@^1.5.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c"
+ dependencies:
+ es-to-primitive "^1.1.1"
+ function-bind "^1.1.0"
+ is-callable "^1.1.3"
+ is-regex "^1.0.3"
+
+es-to-primitive@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
+ dependencies:
+ is-callable "^1.1.1"
+ is-date-object "^1.0.1"
+ is-symbol "^1.0.1"
+
+for-each@~0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4"
+ dependencies:
+ is-function "~1.0.0"
+
+foreach@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+
+function-bind@^1.0.2, function-bind@^1.1.0, function-bind@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
+
+glob@~7.1.2:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+has@^1.0.1, has@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
+ dependencies:
+ function-bind "^1.0.2"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@~2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+is-callable@^1.1.1, is-callable@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
+
+is-date-object@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
+
+is-function@~1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5"
+
+is-regex@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
+ dependencies:
+ has "^1.0.1"
+
+is-symbol@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
+
+lodash@^4.17.4:
+ version "4.17.4"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
+
+minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+
+object-inspect@~1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.2.2.tgz#c82115e4fcc888aea14d64c22e4f17f6a70d5e5a"
+
+object-keys@^1.0.8:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ dependencies:
+ wrappy "1"
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+
+path-parse@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
+
+platform@^1.3.3:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd"
+
+resolve@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
+ dependencies:
+ path-parse "^1.0.5"
+
+resumer@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759"
+ dependencies:
+ through "~2.3.4"
+
+string.prototype.trim@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.5.0"
+ function-bind "^1.0.2"
+
+tape@^4.6.3:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/tape/-/tape-4.7.0.tgz#f3ebb214fef3d6907e5a57dbaafe3bd8a7cbed88"
+ dependencies:
+ deep-equal "~1.0.1"
+ defined "~1.0.0"
+ for-each "~0.3.2"
+ function-bind "~1.1.0"
+ glob "~7.1.2"
+ has "~1.0.1"
+ inherits "~2.0.3"
+ minimist "~1.2.0"
+ object-inspect "~1.2.2"
+ resolve "~1.3.3"
+ resumer "~0.0.0"
+ string.prototype.trim "~1.1.2"
+ through "~2.3.8"
+
+through@~2.3.4, through@~2.3.8:
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"