public List<P> GetClusterResult(Boundary grid)
		{
			// Collect used buckets and return the result
			var clusterPoints = new List<P>();

			// O(m*n)
			foreach (var item in BucketsLookup)
			{
				var bucket = item.Value;
				if (!bucket.IsUsed) continue;

				if (bucket.Points.Count < GmcSettings.Get.MinClusterSize)
				{
					clusterPoints.AddRange(bucket.Points);
				}
				else
				{
					bucket.Centroid.C = bucket.Points.Count;
					clusterPoints.Add(bucket.Centroid);
				}
			}

			// var filtered = FilterDataset(clusterPoints, grid); // post filter data for client viewport
			// return filtered; //not working properly when zoomed far out.
			return clusterPoints;  // return not post filtered
		}
		/// <summary>
		/// todo use threads and threadData
		/// </summary>
		/// <param name="points"></param>
		/// <param name="input"></param>
		public GridCluster(IList<P> points, JsonGetMarkersReceive input)
			: base(points)
		{
			this._jsonReceive = input;
            double[] deltas = GetDelta(GmcSettings.Get.Gridx, GmcSettings.Get.Gridy, input.Zoomlevel);
			DeltaX = deltas[0];
			DeltaY = deltas[1];
			Grid = GetBoundaryExtended(input);
		}
		public static Boundary GetBoundaryExtended(JsonGetMarkersReceive input)
		{            
            var deltas = GetDelta(GmcSettings.Get.Gridx, GmcSettings.Get.Gridy, input.Zoomlevel);
			var deltaX = deltas[0];
			var deltaY = deltas[1];

			// Grid with extended outer grid-area non-visible            
			var a = MathTool.FloorLatLon(input.Viewport.Minx, deltaX) - deltaX * GmcSettings.Get.OuterGridExtend;
			var b = MathTool.FloorLatLon(input.Viewport.Miny, deltaY) - deltaY * GmcSettings.Get.OuterGridExtend;
			var a2 = MathTool.FloorLatLon(input.Viewport.Maxx, deltaX) + deltaX * (1 + GmcSettings.Get.OuterGridExtend);
			var b2 = MathTool.FloorLatLon(input.Viewport.Maxy, deltaY) + deltaY * (1 + GmcSettings.Get.OuterGridExtend);

			// Latitude is special with Google Maps, they don't wrap around, then do constrain
			b = MathTool.ConstrainLatitude(b);
			b2 = MathTool.ConstrainLatitude(b2);

			var grid = new Boundary { Minx = a, Miny = b, Maxx = a2, Maxy = b2 };
			grid.Normalize();
			return grid;
		}
		public JsonGetMarkersReceive(double nelat, double nelon, double swlat, double swlon, int zoomlevel, string filter)
		{
			Zoomlevel = zoomlevel;
			
			Viewport = new Boundary { Minx = swlon, Maxx = nelon, Miny = swlat, Maxy = nelat };

			// Parse filter
			var typeFilter = new HashSet<int>();
			if (string.IsNullOrWhiteSpace(filter))
			{
				// no filter used                
				IsClusteringEnabled = true;
				IsDebugLinesEnabled = false;
			}
			else
			{
				int binarySum = 0;
				int.TryParse(filter, out binarySum);
				string binary = Convert.ToString(binarySum, 2);
				binary = Reverse(binary); // more easy to take index of when reversed

				// First two are reserved for cluster and debug lines flag
				IsClusteringEnabled = binary.Length >= 1 && binary[0] == '1';
				IsDebugLinesEnabled = binary.Length >= 2 && binary[1] == '1';

				// Rest are marker type filter
				var type1 = binary.Length >= 3 && binary[2] == '1';
				var type2 = binary.Length >= 4 && binary[3] == '1';
				var type3 = binary.Length >= 5 && binary[4] == '1';

				if (!type1) typeFilter.Add(1);
				if (!type2) typeFilter.Add(2);
				if (!type3) typeFilter.Add(3);
			}

			TypeFilterExclude = typeFilter;
		}
		/// <summary>
		/// For debugging purpose
		/// Display the red grid lines on the map to show how the points are clustered
		/// </summary>
		/// <returns></returns>
		public IList<Line> GetPolyLines()
		{
			if (!GmcSettings.Get.DoShowGridLinesInGoogleMap) return null; // server disabled it
			if (!_jsonReceive.IsDebugLinesEnabled) return null; // client disabled it

			// Make the red lines data to be drawn in Google map

			var temp = new List<Rectangle>();

			const int borderLinesAdding = 1;
			var linesStepsX = (int)(Math.Round(Grid.AbsX / DeltaX) + borderLinesAdding);
			var linesStepsY = (int)(Math.Round(Grid.AbsY / DeltaY) + borderLinesAdding);

			var b = new Boundary(Grid);
			const double restrictLat = 5.5;	 // heuristic value, Google maps related
			b.Miny = MathTool.ConstrainLatitude(b.Miny, restrictLat); // Make sure it is visible on screen, restrict by some value
			b.Maxy = MathTool.ConstrainLatitude(b.Maxy, restrictLat);

			// Vertical lines
			for (var i = 0; i < linesStepsX; i++)
			{
				var xx = b.Minx + i * DeltaX;

				// Draw region
				if (_jsonReceive.Zoomlevel > 3)	// heuristic value, Google maps related
				{
					temp.Add(new Rectangle { Minx = xx, Miny = b.Miny, Maxx = xx, Maxy = b.Maxy });
				}
				// World wrap issue when same latlon area visible multiple times
				// Make sure line is drawn from left to right on screen
				else
				{
					temp.Add(new Rectangle { Minx = xx, Miny = LatLonInfo.MinLatValue + restrictLat, Maxx = xx, Maxy = 0 });
					temp.Add(new Rectangle { Minx = xx, Miny = 0, Maxx = xx, Maxy = LatLonInfo.MaxLatValue - restrictLat });
				}
			}

			// Horizontal lines            
			for (var i = 0; i < linesStepsY; i++)
			{
				var yy = b.Miny + i * DeltaY;

				// Draw region
				if (_jsonReceive.Zoomlevel > 3)  // heuristic value
				{
					// Don't draw lines outsize the world
					if (MathTool.IsLowerThanLatMin(yy) || MathTool.IsGreaterThanLatMax(yy)) continue;

					temp.Add(new Rectangle { Minx = b.Minx, Miny = yy, Maxx = b.Maxx, Maxy = yy });
				}
				// World wrap issue when same latlon area visible multiple times
				// Make sure line is drawn from left to right on screen
				else
				{
					temp.Add(new Rectangle { Minx = LatLonInfo.MinLonValue, Miny = yy, Maxx = 0, Maxy = yy });
					temp.Add(new Rectangle { Minx = 0, Miny = yy, Maxx = LatLonInfo.MaxLonValue, Maxy = yy });
				}
			}

			var lines = new List<Line>();

			// Normalize the lines and add as string
			foreach (var line in temp)
			{
				var x = (line.Minx).NormalizeLongitude().DoubleToString();
				var x2 = (line.Maxx).NormalizeLongitude().DoubleToString();
				var y = (line.Miny).NormalizeLatitude().DoubleToString();
				var y2 = (line.Maxy).NormalizeLatitude().DoubleToString();
				lines.Add(new Line { X = x, Y = y, X2 = x2, Y2 = y2 });
			}
			return lines;
		}		
		// To work properly it requires the p is already normalized
		protected static int[] GetPointMappedIds(P p, Boundary grid, double deltax, double deltay)
		{
			#region Naive
			// Naive version, lon points near 180 and lat points near 90 are not clustered together
			//idx = (int)(relativeX / deltax);
			//idy = (int)(relativeY / deltay);
			//var relativeX = p.X - grid.Minx;
			#endregion Naive

			/*
			You have to draw a line with longitude values 180, -180 on papir to understand this            
                
			 e.g. _deltaX = 20
longitude        150   170  180  -170   -150
				 |      |          |     |
                 
       
   idx =         7      8    9    -9    -8
							-10    
                                  
here we want idx 8, 9, -10 and -9 be equal to each other, we set them to idx=8
then the longitudes from 170 to -170 will be clustered together
			 */

			var relativeY = p.Y - grid.Miny;

			var overlapMapMinX = (int)(LatLonInfo.MinLonValue / deltax) - 1;
			var overlapMapMaxX = (int)(LatLonInfo.MaxLonValue / deltax);

			// The deltaX = 20 example scenario, then set the value 9 to 8 and -10 to -9            

			// Similar to if (LatLonInfo.MaxLonValue % deltax == 0) without floating presicion issue
			if (Math.Abs(LatLonInfo.MaxLonValue % deltax - 0) < Numbers.Epsilon)
			{
				overlapMapMaxX--;
				overlapMapMinX++;
			}

			var idxx = (int)(p.X / deltax);
			if (p.X < 0) idxx--;

			if (Math.Abs(LatLonInfo.MaxLonValue % p.X - 0) < Numbers.Epsilon)
			{
				if (p.X < 0) idxx++;
				else idxx--;
			}
			if (idxx == overlapMapMinX) idxx = overlapMapMaxX;

			var idx = idxx;

			// Latitude never wraps around with Google Maps, ignore 90, -90 wrap-around for latitude
			var idy = (int)(relativeY / deltay);

			return new[] { idx, idy };
		}
 public static bool IsInside(Boundary b, P p)
 {
     return IsInside(b.Minx, b.Miny, b.Maxx, b.Maxy, p.X, p.Y, false, false);
 }
		// O(n)
        public static IList<P> FilterDataByViewport(IList<P> points, Boundary viewport)
		{
            return points
                 .Where(i => MathTool.IsInside(viewport, i))
                 .ToList();
		}