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"