public IList<P> RunCluster()
		{
			// Skip points outside the grid, not visible to user then skip those
			IList<P> filtered = ClusterInfo.DoFilterData(this._jsonReceive.Zoomlevel)
				? FilterUtil.FilterDataByViewport(this.points, Grid)
				: this.points;

			// Put points in buckets
			foreach (var p in filtered)
			{
				var idxy = GetPointMappedIds(p, Grid, DeltaX, DeltaY);
				var idx = idxy[0];
				var idy = idxy[1];

				// Bucket id
				var id = GetId(idx, idy);

				// Bucket exists, add point
				if (BucketsLookup.ContainsKey(id))
				{
					BucketsLookup[id].Points.Add(p);
				}
				// New bucket, create and add point
				else
				{
					var bucket = new Bucket(idx, idy, id);
					bucket.Points.Add(p);
					BucketsLookup.Add(id, bucket);
				}
			}

			// Calculate centroid for all buckets
			SetCentroidForAllBuckets(BucketsLookup.Values);

			// Merge if gridpoint is to close
			if (GmcSettings.Get.DoMergeGridIfCentroidsAreCloseToEachOther) MergeClustersGrid();

			if (GmcSettings.Get.DoUpdateAllCentroidsToNearestContainingPoint) UpdateAllCentroidsToNearestContainingPoint();

			// Check again
			// Merge if gridpoint is to close
			if (GmcSettings.Get.DoMergeGridIfCentroidsAreCloseToEachOther
				&& GmcSettings.Get.DoUpdateAllCentroidsToNearestContainingPoint)
			{
				MergeClustersGrid();
				// And again set centroid to closest point in bucket 
				UpdateAllCentroidsToNearestContainingPoint();
			}

			return GetClusterResult(Grid);
		}
		// Update centroid location to nearest point, 
		// e.g. if you want to show cluster point on a real existing point area
		// O(n)
		public void UpdateCentroidToNearestContainingPoint(Bucket bucket)
		{
			if (bucket == null || bucket.Centroid == null ||
				bucket.Points == null || bucket.Points.Count == 0)
			{
				return;
			}

			var closest = GetClosestPoint(bucket.Centroid, bucket.Points);
			bucket.Centroid.X = closest.X; // no normalize, points are already normalized by default
			bucket.Centroid.Y = closest.Y;
		}