From ecadd03737db34399b2961347670f35e6ebcc886 Mon Sep 17 00:00:00 2001 From: Lukas Himsel Date: Sat, 19 Mar 2022 12:52:18 +0100 Subject: [PATCH 1/8] port functions --- lib/src/clusters.dart | 313 ++++++++++++++++++++++++++++++ lib/src/meta.dart | 4 +- test/components/cluster_test.dart | 101 ++++++++++ 3 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 lib/src/clusters.dart create mode 100644 test/components/cluster_test.dart diff --git a/lib/src/clusters.dart b/lib/src/clusters.dart new file mode 100644 index 0000000..b1b0d45 --- /dev/null +++ b/lib/src/clusters.dart @@ -0,0 +1,313 @@ +import '../meta.dart'; +import '../helpers.dart'; + +/// +/// Get Cluster +/// +/// @name getCluster +/// @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 +/// + +FeatureCollection getCluster(FeatureCollection geojson, dynamic filter) { + // Filter Features + List features = []; + featureEach(geojson, (feature, i) { + if (applyFilter(feature.properties, filter)) features.add(feature); + }); + return FeatureCollection(features: features); +} + +/** + * clusterEach + * + * @name 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 + * turf.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); + * }); + */ + +/// +/// 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} +/// + +typedef ClusterEachCallback = dynamic Function( + FeatureCollection? cluster, + dynamic clusterValue, + int? currentIndex, +); + +void clusterEach( + FeatureCollection geojson, dynamic property, ClusterEachCallback callback) { + if (property != null) { + throw Exception("property is required"); + } + + // Create clusters based on property values + var bins = createBins(geojson, property); + var values = bins.keys.toList(); + for (var index = 0; index < values.length; index++) { + var value = values[index]; + List bin = bins[value]!; + List features = []; + for (var i = 0; i < bin.length; i++) { + features.add(geojson.features[bin[i]]); + } + callback(FeatureCollection(features: 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.concat(clusterValue); +/// }, []); +/// + +typedef ClusterReduceCallback = T? Function( + T? previousValue, + FeatureCollection? cluster, + dynamic clusterValue, + int? currentIndex, +); + +T? clusterReduce( + FeatureCollection geojson, + dynamic property, + ClusterReduceCallback callback, + dynamic initialValue, +) { + var previousValue = initialValue; + clusterEach(geojson, property, (cluster, clusterValue, currentIndex) { + if (currentIndex == 0 && initialValue == null) { + 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 +/// var 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 ] } +/// + +Map> createBins(FeatureCollection geojson, dynamic property) { + Map> bins = {}; + + featureEach(geojson, (feature, i) { + var properties = feature.properties ?? {}; + if (properties.containsKey(property)) { + var value = properties[property]; + if (bins.containsKey(value)) { + bins[value]!.add(i); + } else { + bins[value] = [i]; + } + } + }); + return bins; +} + +/// +/// Apply Filter +/// +/// @private +/// @param {*} properties Properties +/// @param {*} filter Filter +/// @returns {boolean} applied Filter to properties +/// + +bool applyFilter(Map? properties, dynamic filter) { + if (properties == null) return false; + if (filter is num || filter is String) { + return properties.containsKey(filter); + } else if (filter is List) { + for (var i = 0; i < filter.length; i++) { + if (!applyFilter(properties, filter[i])) return false; + } + return true; + } 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 +/// +bool propertiesContainsFilter(Map properties, Map filter) { + var keys = filter.keys.toList(); + 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 {Array} keys Used to filter Properties +/// @returns {*} filtered Properties +/// @example +/// filterProperties({foo: 'bar', cluster: 0}, ['cluster']) +/// //= {cluster: 0} +/// + +Map filterProperties( + Map properties, List keys) { + if (keys.isEmpty) return {}; + + Map newProperties = {}; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (properties.containsKey(key)) { + newProperties[key] = properties[key]; + } + } + return newProperties; +} diff --git a/lib/src/meta.dart b/lib/src/meta.dart index d020b91..b703d3a 100644 --- a/lib/src/meta.dart +++ b/lib/src/meta.dart @@ -246,7 +246,7 @@ void _forEachGeomInGeometryObject( /// Callback for propEach typedef PropEachCallback = dynamic Function( - Map? currentProperties, num featureIndex); + Map? currentProperties, int featureIndex); /// Iterate over properties in any [geoJSON] object, calling [callback] on each /// iteration. Similar to Array.forEach() @@ -279,7 +279,7 @@ void propEach(GeoJSONObject geoJSON, PropEachCallback callback) { /// Callback for featureEach typedef FeatureEachCallback = dynamic Function( - Feature currentFeature, num featureIndex); + Feature currentFeature, int featureIndex); /// Iterate over features in any [geoJSON] object, calling [callback] on each /// iteration. Similar to Array.forEach. diff --git a/test/components/cluster_test.dart b/test/components/cluster_test.dart new file mode 100644 index 0000000..da3a99c --- /dev/null +++ b/test/components/cluster_test.dart @@ -0,0 +1,101 @@ +import 'package:turf/helpers.dart'; +import 'package:test/test.dart'; +import 'package:turf/src/clusters.dart'; + +final properties = {"foo": "bar", "cluster": 0}; +final geojson = FeatureCollection(features: [ + Feature( + geometry: Point(coordinates: Position.of([0, 0])), + properties: {"cluster": 0, "foo": "null"}), + Feature( + geometry: Point(coordinates: Position.of([2, 4])), + properties: {"cluster": 1, "foo": "bar"}), + Feature( + geometry: Point(coordinates: Position.of([3, 6])), + properties: {"cluster": 1}), + Feature( + geometry: Point(coordinates: Position.of([5, 1])), + properties: {"0": "foo"}), + Feature( + geometry: Point(coordinates: Position.of([4, 2])), + properties: {"bar": "foo"}), + Feature(geometry: Point(coordinates: Position.of([2, 4])), properties: {}), + Feature(geometry: Point(coordinates: Position.of([4, 3])), properties: null), +]); + +main() { + test("clusters -- getCluster", () { + expect(getCluster(geojson, 0).features.length, 1); + expect(getCluster(geojson, 1).features.length, 0); + expect(getCluster(geojson, "bar").features.length, 1); + expect(getCluster(geojson, "cluster").features.length, 3); + expect(getCluster(geojson, {"cluster": 1}).features.length, 2); + expect(getCluster(geojson, {"cluster": 0}).features.length, 1); + expect( + getCluster(geojson, [ + "cluster", + {"foo": "bar"} + ]).features.length, + 1); + expect(getCluster(geojson, ["cluster", "foo"]).features.length, 2); + expect(getCluster(geojson, ["cluster"]).features.length, 3); + }); + + test("clusters -- clusterEach", () { + const clusters = []; + int total = 0; + clusterEach(geojson, "cluster", (cluster, clusterValue, currentIndex) { + total += cluster!.features.length; + clusters.add(cluster); + expect(cluster.features.isNotEmpty, true); + }); + expect(total, 3); + expect(clusters.length, 2); + }); + +/* +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(); +}); + +test("clusters.utils -- 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(); +}); + +test("clusters.utils -- filterProperties", (t) => { + t.deepEqual(filterProperties(properties, ["cluster"]), { cluster: 0 }); + t.deepEqual(filterProperties(properties, []), {}); + t.deepEqual(filterProperties(properties, undefined), {}); + t.end(); +}); + +test("clusters.utils -- 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(); +}); + +test("clusters.utils -- propertiesContainsFilter", (t) => { + t.deepEqual(createBins(geojson, "cluster"), { 0: [0], 1: [1, 2] }); + t.end(); +}); +*/ +} From ac041dcf455729dc4d3b780dc85ca9e9c78abc1e Mon Sep 17 00:00:00 2001 From: armantorkzaban Date: Fri, 25 Mar 2022 14:59:00 +0100 Subject: [PATCH 2/8] getCluster documentation --- lib/src/clusters.dart | 167 ++++++++++++++++++++++-------------------- 1 file changed, 86 insertions(+), 81 deletions(-) diff --git a/lib/src/clusters.dart b/lib/src/clusters.dart index b1b0d45..990b315 100644 --- a/lib/src/clusters.dart +++ b/lib/src/clusters.dart @@ -1,33 +1,43 @@ import '../meta.dart'; import '../helpers.dart'; -/// /// Get Cluster +/// Takes a [FeatureCollection(features: [ +/// Feature( +/// geometry: Point(coordinates: Position.of([10, 10])), +/// properties: {'marker-symbol': 'circle'}, +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([20, 20])), +/// properties: {'marker-symbol': 'circle'}, +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([30, 30])), +/// properties: {'marker-symbol': 'circle'}, +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([40, 40])), +/// properties: {'marker-symbol': 'circle'}, +/// ), +/// ]); /// /// // Create a cluster using K-Means (adds `cluster` to GeoJSON properties) -/// var clustered = turf.clustersKmeans(geojson); +/// var clustered = clustersKmeans(geojson); /// /// // Retrieve first cluster (0) -/// var cluster = turf.getCluster(clustered, {cluster: 0}); +/// var cluster = getCluster(clustered, {cluster: 0}); /// //= cluster /// /// // Retrieve cluster based on custom properties -/// turf.getCluster(clustered, {'marker-symbol': 'circle'}).length; +/// getCluster(clustered, {'marker-symbol': 'circle'}).length; /// //= 2 -/// turf.getCluster(clustered, {'marker-symbol': 'square'}).length; +/// getCluster(clustered, {'marker-symbol': 'square'}).length; /// //= 1 /// @@ -40,45 +50,44 @@ FeatureCollection getCluster(FeatureCollection geojson, dynamic filter) { return FeatureCollection(features: features); } -/** - * clusterEach - * - * @name 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 - * turf.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); - * }); - */ +/// clusterEach +/// +/// @name 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 = featureCollection([ +/// point([0, 0]), +/// point([2, 4]), +/// point([3, 6]), +/// point([5, 1]), +/// 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 +/// clusterEach(clustered, 'cluster', function () { +/// total++; +/// }); +/// +/// // Create an Array of all the values retrieved from the 'cluster' property +/// var values = [] +/// clusterEach(clustered, 'cluster', function (cluster, clusterValue) { +/// values.push(clusterValue); +/// }); +/// /// /// Callback for clusterEach @@ -88,7 +97,6 @@ FeatureCollection getCluster(FeatureCollection geojson, dynamic filter) { /// @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} -/// typedef ClusterEachCallback = dynamic Function( FeatureCollection? cluster, @@ -116,30 +124,27 @@ void clusterEach( } } -/** - * 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. - */ - +/// 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 @@ -149,7 +154,7 @@ void clusterEach( /// @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([ +/// var geojson = featureCollection([ /// turf.point([0, 0]), /// turf.point([2, 4]), /// turf.point([3, 6]), From 3f5706f1c7d05ff5d8b922b7868c455714eaba62 Mon Sep 17 00:00:00 2001 From: armantorkzaban Date: Tue, 29 Mar 2022 18:38:25 +0200 Subject: [PATCH 3/8] WIP documentation --- lib/src/clusters.dart | 79 ++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/lib/src/clusters.dart b/lib/src/clusters.dart index 990b315..c1c134b 100644 --- a/lib/src/clusters.dart +++ b/lib/src/clusters.dart @@ -2,9 +2,8 @@ import '../meta.dart'; import '../helpers.dart'; /// Get Cluster -/// Takes a [FeatureCollection] and a Filter used on GeoJSON properties to get Cluster. +/// Returns [FeatureCollection] single cluster filtered by GeoJSON Properties /// For example: /// /// ```dart @@ -34,12 +33,12 @@ import '../helpers.dart'; /// var cluster = getCluster(clustered, {cluster: 0}); /// //= cluster /// -/// // Retrieve cluster based on custom properties +/// // Retrieves cluster based on custom properties /// getCluster(clustered, {'marker-symbol': 'circle'}).length; /// //= 2 /// getCluster(clustered, {'marker-symbol': 'square'}).length; /// //= 1 -/// +/// ``` FeatureCollection getCluster(FeatureCollection geojson, dynamic filter) { // Filter Features @@ -57,20 +56,33 @@ FeatureCollection getCluster(FeatureCollection geojson, dynamic filter) { /// @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 = featureCollection([ -/// point([0, 0]), -/// point([2, 4]), -/// point([3, 6]), -/// point([5, 1]), -/// point([4, 2]) -/// ]); +/// For example: +/// +/// ```dart +/// var geojson = FeatureCollection(features: [ +/// Feature( +/// geometry: Point(coordinates: Position.of([10, 10])), +/// properties: {'marker-symbol': 'circle'}, +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([20, 20])), +/// properties: {'marker-symbol': 'circle'}, +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([30, 30])), +/// properties: {'marker-symbol': 'circle'}, +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([40, 40])), +/// properties: {'marker-symbol': 'circle'}, +/// ), +/// ]); /// /// // Create a cluster using K-Means (adds `cluster` to GeoJSON properties) -/// var clustered = turf.clustersKmeans(geojson); +/// var clustered = clustersKmeans(geojson); /// -/// // Iterate over each cluster -/// clusterEach(clustered, 'cluster', function (cluster, clusterValue, currentIndex) { +/// // Iterates over each cluster +/// clusterEach(clustered, 'cluster', (cluster, clusterValue, currentIndex) { /// //= cluster /// //= clusterValue /// //= currentIndex @@ -82,21 +94,19 @@ FeatureCollection getCluster(FeatureCollection geojson, dynamic filter) { /// total++; /// }); /// -/// // Create an Array of all the values retrieved from the 'cluster' property +/// // Create [List] of all the values retrieved from the 'cluster' property /// var values = [] -/// clusterEach(clustered, 'cluster', function (cluster, clusterValue) { +/// clusterEach(clustered, 'cluster', (cluster, clusterValue) { /// values.push(clusterValue); /// }); -/// +/// ``` +/// ClusterEachCallback /// -/// 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} +/// Takes [FeatureCollection], the [cluster] being processed, a [clusterValue] +/// used to create cluster being processed, and the [currentIndex], the index of +/// current element being processed in the [List]. Starts at index 0 +/// Returns void. typedef ClusterEachCallback = dynamic Function( FeatureCollection? cluster, @@ -124,18 +134,17 @@ void clusterEach( } } -/// Callback for clusterReduce -/// +/// ClusterReduceCallback /// The first time the callback function is called, the values provided as arguments depend -/// on whether the reduce method has an initialValue argument. +/// 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 provided to the reduce method: +/// - The [previousValue] argument is [initialValue]. +/// - The [currentValue] argument is the value of the first element present in the [List]. /// -/// 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. +/// If an [initialValue] is not provided: +/// - The [previousValue] argument is the value of the first element present in the [List]. +/// - The [currentValue] argument is the value of the second element present in the [List]. /// /// @callback clusterReduceCallback /// @param {*} [previousValue] The accumulated value previously returned in the last invocation From 4c5eeadd74a8208bf05a3295b5ff3a21c54d2ccc Mon Sep 17 00:00:00 2001 From: armantorkzaban Date: Wed, 30 Mar 2022 18:41:28 +0200 Subject: [PATCH 4/8] Did the documentation --- lib/src/clusters.dart | 218 +++++++++++++++++++++--------------------- 1 file changed, 108 insertions(+), 110 deletions(-) diff --git a/lib/src/clusters.dart b/lib/src/clusters.dart index c1c134b..62e7c9f 100644 --- a/lib/src/clusters.dart +++ b/lib/src/clusters.dart @@ -2,8 +2,9 @@ import '../meta.dart'; import '../helpers.dart'; /// Get Cluster -/// Takes a [FeatureCollection] and a Filter used on GeoJSON properties to get Cluster. -/// Returns [FeatureCollection] single cluster filtered by GeoJSON Properties +/// Takes a [FeatureCollection] and a [dynamic] [filter] used on GeoJSON properties +/// to get Cluster. +/// Returns a [FeatureCollection] single cluster filtered by GeoJSON Properties /// For example: /// /// ```dart @@ -18,18 +19,18 @@ import '../helpers.dart'; /// ), /// Feature( /// geometry: Point(coordinates: Position.of([30, 30])), -/// properties: {'marker-symbol': 'circle'}, +/// properties: {'marker-symbol': 'square'}, /// ), /// Feature( /// geometry: Point(coordinates: Position.of([40, 40])), -/// properties: {'marker-symbol': 'circle'}, +/// properties: {'marker-symbol': 'triangle'}, /// ), /// ]); /// -/// // Create a cluster using K-Means (adds `cluster` to GeoJSON properties) +/// // Creates a cluster using K-Means (adds `cluster` to GeoJSON properties) /// var clustered = clustersKmeans(geojson); /// -/// // Retrieve first cluster (0) +/// // Retrieves first cluster (0) /// var cluster = getCluster(clustered, {cluster: 0}); /// //= cluster /// @@ -49,36 +50,40 @@ FeatureCollection getCluster(FeatureCollection geojson, dynamic filter) { return FeatureCollection(features: features); } +/// ClusterEachCallback +/// Takes a [FeatureCollection], the cluster being processed, a [clusterValue] +/// used to create cluster being processed, and the [currentIndex], the index of +/// current element being processed in the [List]. Starts at index 0 +/// Returns void. +typedef ClusterEachCallback = dynamic Function( + FeatureCollection? cluster, + dynamic clusterValue, + int? currentIndex, +); + /// clusterEach -/// -/// @name 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} +/// Takes a [FeatureCollection], a dynamic [property] key/value used to create clusters, +/// and a [ClusterEachCallback] method that takes (cluster, clusterValue, currentIndex) and +/// Returns void. /// For example: /// /// ```dart /// var geojson = FeatureCollection(features: [ /// Feature( /// geometry: Point(coordinates: Position.of([10, 10])), -/// properties: {'marker-symbol': 'circle'}, /// ), /// Feature( /// geometry: Point(coordinates: Position.of([20, 20])), -/// properties: {'marker-symbol': 'circle'}, /// ), /// Feature( /// geometry: Point(coordinates: Position.of([30, 30])), -/// properties: {'marker-symbol': 'circle'}, /// ), /// Feature( /// geometry: Point(coordinates: Position.of([40, 40])), -/// properties: {'marker-symbol': 'circle'}, /// ), /// ]); /// -/// // Create a cluster using K-Means (adds `cluster` to GeoJSON properties) +/// // Create a cluster using K-Means (adds `cluster` to [GeoJSONObject]'s properties) /// var clustered = clustersKmeans(geojson); /// /// // Iterates over each cluster @@ -88,39 +93,26 @@ FeatureCollection getCluster(FeatureCollection geojson, dynamic filter) { /// //= currentIndex /// }) /// -/// // Calculate the total number of clusters +/// // Calculates the total number of clusters /// var total = 0 /// clusterEach(clustered, 'cluster', function () { /// total++; /// }); /// -/// // Create [List] of all the values retrieved from the 'cluster' property +/// // Creates [List] of all the values retrieved from the 'cluster' property /// var values = [] /// clusterEach(clustered, 'cluster', (cluster, clusterValue) { -/// values.push(clusterValue); +/// values.add(clusterValue); /// }); /// ``` -/// ClusterEachCallback -/// -/// Takes [FeatureCollection], the [cluster] being processed, a [clusterValue] -/// used to create cluster being processed, and the [currentIndex], the index of -/// current element being processed in the [List]. Starts at index 0 -/// Returns void. - -typedef ClusterEachCallback = dynamic Function( - FeatureCollection? cluster, - dynamic clusterValue, - int? currentIndex, -); - void clusterEach( FeatureCollection geojson, dynamic property, ClusterEachCallback callback) { if (property != null) { throw Exception("property is required"); } - // Create clusters based on property values + // Creates clusters based on property values var bins = createBins(geojson, property); var values = bins.keys.toList(); for (var index = 0; index < values.length; index++) { @@ -146,37 +138,48 @@ void clusterEach( /// - The [previousValue] argument is the value of the first element present in the [List]. /// - The [currentValue] argument is the value of the second element present in the [List]. /// -/// @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. +/// Takes a [previousValue], the accumulated value previously returned in the last invocation +/// of the callback, or [initialValue], if supplied, a [FeatureCollection] [cluster], the current +/// cluster being processed, a [clusterValue] used to create cluster being processed and a +/// [currentIndex], the index of the current element being processed in the +/// [List]. +/// Starts at index 0, if an [initialValue] is provided, and at index 1 otherwise. +typedef ClusterReduceCallback = T? Function( + T? previousValue, + FeatureCollection? cluster, + dynamic clusterValue, + int? currentIndex, +); -/// Reduce clusters in GeoJSON Features, similar to Array.reduce() +/// Reduces clusters in Features, similar to [Iterable.reduce] +/// Takes a [FeatureCollection][geojson], a dynamic [porperty], a [GeoJSONObject]'s property key/value +/// used to create clusters, a [ClusterReduceCallback] method, and an [initialValue] to +/// use as the first argument to the first call of the callback. +/// Returns the value that results from the reduction. +/// For example: /// -/// @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 = featureCollection([ -/// turf.point([0, 0]), -/// turf.point([2, 4]), -/// turf.point([3, 6]), -/// turf.point([5, 1]), -/// turf.point([4, 2]) -/// ]); +/// ```dart +/// var geojson = FeatureCollection(features: [ +/// Feature( +/// geometry: Point(coordinates: Position.of([10, 10])), +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([20, 20])), +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([30, 30])), +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([40, 40])), +/// ), +/// ]); /// -/// // Create a cluster using K-Means (adds `cluster` to GeoJSON properties) -/// var clustered = turf.clustersKmeans(geojson); +/// // Creates a cluster using K-Means (adds `cluster` to GeoJSON properties) +/// var clustered = clustersKmeans(geojson); /// -/// // Iterate over each cluster and perform a calculation +/// // Iterates over each cluster and perform a calculation /// var initialValue = 0 -/// turf.clusterReduce(clustered, 'cluster', function (previousValue, cluster, clusterValue, currentIndex) { +/// clusterReduce(clustered, 'cluster', (previousValue, cluster, clusterValue, currentIndex) { /// //=previousValue /// //=cluster /// //=clusterValue @@ -184,23 +187,16 @@ void clusterEach( /// return previousValue++; /// }, initialValue); /// -/// // Calculate the total number of clusters -/// var total = turf.clusterReduce(clustered, 'cluster', function (previousValue) { +/// // Calculates the total number of clusters +/// var total = 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.concat(clusterValue); +/// // Creates a [List] of all the values retrieved from the 'cluster' property. +/// var values = clusterReduce(clustered, 'cluster', (previousValue, cluster, clusterValue){ +/// return previousValue.addAll(clusterValue); /// }, []); -/// - -typedef ClusterReduceCallback = T? Function( - T? previousValue, - FeatureCollection? cluster, - dynamic clusterValue, - int? currentIndex, -); +/// ``` T? clusterReduce( FeatureCollection geojson, @@ -220,23 +216,33 @@ T? clusterReduce( return previousValue; } +/// createBins +/// Takes a [FeatureCollection] geojson, dynamic [property] values that are used +/// to create bins. +/// Returns a [Map>] bins with Feature IDs +/// For example /// -/// 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 -/// var 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}), -/// ]); +/// ```dart +/// /// var geojson = FeatureCollection(features: [ +/// Feature( +/// geometry: Point(coordinates: Position.of([10, 10])), +/// properties:{'cluster': 0, 'foo': 'null'}, +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([20, 20])), +/// properties: {'cluster': 1, 'foo': 'bar'}, +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([30, 30])), +/// properties: {'0': 'foo'}, +/// ), +/// Feature( +/// geometry: Point(coordinates: Position.of([40, 40])), +/// properties: {'cluster': 1}, +/// ), +/// ]); /// createBins(geojson, 'cluster'); /// //= { '0': [ 0 ], '1': [ 1, 3 ] } -/// Map> createBins(FeatureCollection geojson, dynamic property) { Map> bins = {}; @@ -255,14 +261,9 @@ Map> createBins(FeatureCollection geojson, dynamic property) { return bins; } -/// -/// Apply Filter -/// -/// @private -/// @param {*} properties Properties -/// @param {*} filter Filter -/// @returns {boolean} applied Filter to properties -/// +/// applyFilter +/// Takes a [Map] [properties] and a [filter], +/// Returns a [bool] indicating filter is applied to the properties. bool applyFilter(Map? properties, dynamic filter) { if (properties == null) return false; @@ -278,19 +279,17 @@ bool applyFilter(Map? properties, dynamic filter) { } } -/// /// Properties contains filter (does not apply deepEqual operations) +/// Takes a [Map] [properties] value, and a [Map] filter and +/// Returns [bool] if filter does equal the [properties] +/// For example /// -/// @private -/// @param {*} properties Properties -/// @param {Object} filter Filter -/// @returns {boolean} does filter equal Properties -/// @example +/// ```dart /// propertiesContainsFilter({foo: 'bar', cluster: 0}, {cluster: 0}) /// //= true /// propertiesContainsFilter({foo: 'bar', cluster: 0}, {cluster: 1}) /// //= false -/// +/// ``` bool propertiesContainsFilter(Map properties, Map filter) { var keys = filter.keys.toList(); for (var i = 0; i < keys.length; i++) { @@ -300,17 +299,16 @@ bool propertiesContainsFilter(Map properties, Map filter) { return true; } +/// filterProperties +/// Takes [Map] [properties], and [List] [keys] used to +/// filter Properties. +/// Returns [Map] filtered Properties +/// For example: /// -/// Filter Properties -/// -/// @private -/// @param {*} properties Properties -/// @param {Array} keys Used to filter Properties -/// @returns {*} filtered Properties -/// @example +/// ```dart /// filterProperties({foo: 'bar', cluster: 0}, ['cluster']) /// //= {cluster: 0} -/// +/// ``` Map filterProperties( Map properties, List keys) { From d205518db15faa2e5b8f12d21c573083bdbbe92b Mon Sep 17 00:00:00 2001 From: armantorkzaban Date: Thu, 31 Mar 2022 11:09:10 +0200 Subject: [PATCH 5/8] wrote tests --- lib/src/clusters.dart | 34 ++++++++----- test/components/cluster_test.dart | 83 +++++++++++++++---------------- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/lib/src/clusters.dart b/lib/src/clusters.dart index 62e7c9f..29b6b76 100644 --- a/lib/src/clusters.dart +++ b/lib/src/clusters.dart @@ -108,7 +108,7 @@ typedef ClusterEachCallback = dynamic Function( void clusterEach( FeatureCollection geojson, dynamic property, ClusterEachCallback callback) { - if (property != null) { + if (property == null) { throw Exception("property is required"); } @@ -217,13 +217,13 @@ T? clusterReduce( } /// createBins -/// Takes a [FeatureCollection] geojson, dynamic [property] values that are used -/// to create bins. -/// Returns a [Map>] bins with Feature IDs +/// Takes a [FeatureCollection] geojson, and dynamic [property] key whose +/// corresponding values of the [Feature]s will be used to create bins. +/// Returns Map> bins with Feature IDs /// For example /// /// ```dart -/// /// var geojson = FeatureCollection(features: [ +/// var geojson = FeatureCollection(features: [ /// Feature( /// geometry: Point(coordinates: Position.of([10, 10])), /// properties:{'cluster': 0, 'foo': 'null'}, @@ -243,9 +243,11 @@ T? clusterReduce( /// ]); /// createBins(geojson, 'cluster'); /// //= { '0': [ 0 ], '1': [ 1, 3 ] } +/// ``` -Map> createBins(FeatureCollection geojson, dynamic property) { - Map> bins = {}; +Map> createBins( + FeatureCollection geojson, dynamic property) { + Map> bins = {}; featureEach(geojson, (feature, i) { var properties = feature.properties ?? {}; @@ -267,16 +269,22 @@ Map> createBins(FeatureCollection geojson, dynamic property) { bool applyFilter(Map? properties, dynamic filter) { if (properties == null) return false; - if (filter is num || filter is String) { + if (filter is! List && filter is! Map && filter is! String) { + throw Exception("filter('s) key must be String"); + } + if (filter is String) { return properties.containsKey(filter); - } else if (filter is List) { + } + if (filter is List) { for (var i = 0; i < filter.length; i++) { if (!applyFilter(properties, filter[i])) return false; } return true; - } else { + } + if (filter is Map) { return propertiesContainsFilter(properties, filter); } + return false; } /// Properties contains filter (does not apply deepEqual operations) @@ -311,11 +319,11 @@ bool propertiesContainsFilter(Map properties, Map filter) { /// ``` Map filterProperties( - Map properties, List keys) { - if (keys.isEmpty) return {}; + Map properties, List? keys) { + if (keys == null || keys.isEmpty) return {}; Map newProperties = {}; - for (var i = 0; i < keys.length; i++) { + for (var i = 0; i < keys!.length; i++) { var key = keys[i]; if (properties.containsKey(key)) { newProperties[key] = properties[key]; diff --git a/test/components/cluster_test.dart b/test/components/cluster_test.dart index da3a99c..3728fb8 100644 --- a/test/components/cluster_test.dart +++ b/test/components/cluster_test.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:turf/helpers.dart'; import 'package:test/test.dart'; import 'package:turf/src/clusters.dart'; @@ -25,8 +27,8 @@ final geojson = FeatureCollection(features: [ main() { test("clusters -- getCluster", () { - expect(getCluster(geojson, 0).features.length, 1); - expect(getCluster(geojson, 1).features.length, 0); + expect(getCluster(geojson, '0').features.length, 1); + expect(() => getCluster(geojson, 1), throwsA(isA())); expect(getCluster(geojson, "bar").features.length, 1); expect(getCluster(geojson, "cluster").features.length, 3); expect(getCluster(geojson, {"cluster": 1}).features.length, 2); @@ -42,7 +44,7 @@ main() { }); test("clusters -- clusterEach", () { - const clusters = []; + List clusters = []; int total = 0; clusterEach(geojson, "cluster", (cluster, clusterValue, currentIndex) { total += cluster!.features.length; @@ -53,49 +55,42 @@ main() { expect(clusters.length, 2); }); -/* -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(); -}); + test("clusters -- clusterReduce", () { + List clusters = []; + var total = clusterReduce(geojson, "cluster", + (previousValue, cluster, clusterValue, currentIndex) { + clusters.add(cluster); + return previousValue! + cluster!.features.length; + }, 0); + expect(total, 3); + expect(clusters.length, 2); + }); -test("clusters.utils -- 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(); -}); + test("clusters.utils -- applyFilter", () { + expect(applyFilter(properties, ["cluster"]), isTrue); + expect(applyFilter(properties, {"cluster": 1}), isFalse); + expect(applyFilter(properties, {"cluster": 0}), isTrue); + expect(applyFilter(null, {"cluster": 0}), isFalse); + }); -test("clusters.utils -- filterProperties", (t) => { - t.deepEqual(filterProperties(properties, ["cluster"]), { cluster: 0 }); - t.deepEqual(filterProperties(properties, []), {}); - t.deepEqual(filterProperties(properties, undefined), {}); - t.end(); -}); + test("clusters.utils -- filterProperties", () { + expect(filterProperties(properties, ["cluster"]), equals({"cluster": 0})); + expect(filterProperties(properties, []), equals({})); + expect(filterProperties(properties, null), equals({})); + }); -test("clusters.utils -- 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(); -}); + test("clusters.utils -- propertiesContainsFilter", () { + expect(propertiesContainsFilter(properties, {"cluster": 0}), isTrue); + expect(propertiesContainsFilter(properties, {"cluster": 1}), isFalse); + expect(propertiesContainsFilter(properties, {"bar": "foo"}), isFalse); + }); -test("clusters.utils -- propertiesContainsFilter", (t) => { - t.deepEqual(createBins(geojson, "cluster"), { 0: [0], 1: [1, 2] }); - t.end(); -}); -*/ + test("clusters.utils -- propertiesContainsFilter", () { + expect( + createBins(geojson, "cluster"), + equals({ + 0: [0], + 1: [1, 2] + })); + }); } From 743567536a83976b57c83d85fdcc757a49ab5322 Mon Sep 17 00:00:00 2001 From: armantorkzaban Date: Thu, 31 Mar 2022 14:23:05 +0200 Subject: [PATCH 6/8] benchmarks - in process --- benchmark/meta_benchmark.dart | 33 +++++++++++++++++++++++++++---- lib/src/clusters.dart | 2 +- test/components/cluster_test.dart | 8 ++++---- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/benchmark/meta_benchmark.dart b/benchmark/meta_benchmark.dart index 0b6132a..2751807 100644 --- a/benchmark/meta_benchmark.dart +++ b/benchmark/meta_benchmark.dart @@ -5,10 +5,12 @@ import 'package:turf/meta.dart'; import 'dart:convert'; import 'dart:io'; +import 'package:turf/src/clusters.dart'; + void main() { - Point pt = Point.fromJson({ - 'coordinates': [0, 0] - }); + Point pt = Point( + coordinates: Position(0, 0), + ); Feature featurePt = Feature(geometry: pt.clone()); @@ -17,7 +19,8 @@ void main() { for (int i = 0; i < 1000; i++) { points.add(pt.clone()); - pointFeatures.add(Feature(geometry: pt.clone())); + pointFeatures + .add(Feature(geometry: pt.clone(), properties: {"cluster": 0})); } GeometryCollection geomCollection = GeometryCollection( @@ -120,4 +123,26 @@ void main() { featureEach(featureCollection, featureEachNoopCB); }); }); + + group('cluster', () { + benchmark('getCluster', () { + getCluster(featureCollection, '0'); + }); + + benchmark('clusterEach', () { + List clusters = []; + int total = 0; + clusterEach(featureCollection, "cluster", + (cluster, clusterValue, currentIndex) { + total += cluster!.features.length; + clusters.add(cluster); + }); + }); + List clusters = []; + clusterReduce(featureCollection, "cluster", + (previousValue, cluster, clusterValue, currentIndex) { + clusters.add(cluster); + return previousValue! + cluster!.features.length; + }, 0); + }); } diff --git a/lib/src/clusters.dart b/lib/src/clusters.dart index 29b6b76..49b0d06 100644 --- a/lib/src/clusters.dart +++ b/lib/src/clusters.dart @@ -323,7 +323,7 @@ Map filterProperties( if (keys == null || keys.isEmpty) return {}; Map newProperties = {}; - for (var i = 0; i < keys!.length; i++) { + for (var i = 0; i < keys.length; i++) { var key = keys[i]; if (properties.containsKey(key)) { newProperties[key] = properties[key]; diff --git a/test/components/cluster_test.dart b/test/components/cluster_test.dart index 3728fb8..05f8266 100644 --- a/test/components/cluster_test.dart +++ b/test/components/cluster_test.dart @@ -66,26 +66,26 @@ main() { expect(clusters.length, 2); }); - test("clusters.utils -- applyFilter", () { + test("applyFilter", () { expect(applyFilter(properties, ["cluster"]), isTrue); expect(applyFilter(properties, {"cluster": 1}), isFalse); expect(applyFilter(properties, {"cluster": 0}), isTrue); expect(applyFilter(null, {"cluster": 0}), isFalse); }); - test("clusters.utils -- filterProperties", () { + test("filterProperties", () { expect(filterProperties(properties, ["cluster"]), equals({"cluster": 0})); expect(filterProperties(properties, []), equals({})); expect(filterProperties(properties, null), equals({})); }); - test("clusters.utils -- propertiesContainsFilter", () { + test("propertiesContainsFilter", () { expect(propertiesContainsFilter(properties, {"cluster": 0}), isTrue); expect(propertiesContainsFilter(properties, {"cluster": 1}), isFalse); expect(propertiesContainsFilter(properties, {"bar": "foo"}), isFalse); }); - test("clusters.utils -- propertiesContainsFilter", () { + test("propertiesContainsFilter", () { expect( createBins(geojson, "cluster"), equals({ From a538540b101b32e09af9ecb3521e86aad58d167e Mon Sep 17 00:00:00 2001 From: armantorkzaban Date: Fri, 1 Apr 2022 10:31:44 +0200 Subject: [PATCH 7/8] moved benchmarks to separate file --- benchmark/cluster_benchmark.dart | 44 ++++++++++++++++++++++++++++++++ benchmark/meta_benchmark.dart | 24 ----------------- 2 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 benchmark/cluster_benchmark.dart diff --git a/benchmark/cluster_benchmark.dart b/benchmark/cluster_benchmark.dart new file mode 100644 index 0000000..0f96f0b --- /dev/null +++ b/benchmark/cluster_benchmark.dart @@ -0,0 +1,44 @@ +import 'package:benchmark/benchmark.dart'; +import 'package:turf/helpers.dart'; +import 'package:turf/src/clusters.dart'; + +void main() { + Point pt = Point( + coordinates: Position(0, 0), + ); + + List points = []; + List> pointFeatures = []; + + for (int i = 0; i < 1000; i++) { + points.add(pt.clone()); + pointFeatures + .add(Feature(geometry: pt.clone(), properties: {"cluster": 0})); + } + + FeatureCollection featureCollection = FeatureCollection( + features: pointFeatures, + ); + + group('cluster', () { + benchmark('getCluster', () { + getCluster(featureCollection, '0'); + }); + + benchmark('clusterEach', () { + List clusters = []; + int total = 0; + clusterEach(featureCollection, "cluster", + (cluster, clusterValue, currentIndex) { + total += cluster!.features.length; + clusters.add(cluster); + }); + }); + List clusters = []; + clusterReduce(featureCollection, "cluster", + (previousValue, cluster, clusterValue, currentIndex) { + clusters.add(cluster); + return previousValue! + cluster!.features.length; + }, 0); + }); +} diff --git a/benchmark/meta_benchmark.dart b/benchmark/meta_benchmark.dart index 2751807..04eb949 100644 --- a/benchmark/meta_benchmark.dart +++ b/benchmark/meta_benchmark.dart @@ -5,8 +5,6 @@ import 'package:turf/meta.dart'; import 'dart:convert'; import 'dart:io'; -import 'package:turf/src/clusters.dart'; - void main() { Point pt = Point( coordinates: Position(0, 0), @@ -123,26 +121,4 @@ void main() { featureEach(featureCollection, featureEachNoopCB); }); }); - - group('cluster', () { - benchmark('getCluster', () { - getCluster(featureCollection, '0'); - }); - - benchmark('clusterEach', () { - List clusters = []; - int total = 0; - clusterEach(featureCollection, "cluster", - (cluster, clusterValue, currentIndex) { - total += cluster!.features.length; - clusters.add(cluster); - }); - }); - List clusters = []; - clusterReduce(featureCollection, "cluster", - (previousValue, cluster, clusterValue, currentIndex) { - clusters.add(cluster); - return previousValue! + cluster!.features.length; - }, 0); - }); } From 45e682ccd7da0688137b35b8cabf3c10c2a08169 Mon Sep 17 00:00:00 2001 From: armantorkzaban Date: Fri, 1 Apr 2022 10:39:56 +0200 Subject: [PATCH 8/8] exported clusters.dart --- lib/clusters.dart | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 lib/clusters.dart diff --git a/lib/clusters.dart b/lib/clusters.dart new file mode 100644 index 0000000..65f8481 --- /dev/null +++ b/lib/clusters.dart @@ -0,0 +1,3 @@ +library turf_clusters; + +export 'package:turf/src/clusters.dart'; //TODO: should we show helpers too?