public GridCluster(IPoints dataset, JsonGetMarkersReceive jsonReceive)
            : base(dataset)
        {
            // Important, set _delta and _grid values in constructor as first step
            var deltas = GetDelta(jsonReceive);
            DeltaX = deltas[0];
            DeltaY = deltas[1];
            Grid = GetBoundaryExtended(jsonReceive);
            Lines = new List<Line>();

            if (AlgoConfig.DoShowGridLinesInGoogleMap) MakeLines(jsonReceive);
        }
        public static Boundary GetBoundaryExtended(JsonGetMarkersReceive jsonReceive)
        {
            var deltas = GetDelta(jsonReceive);
            var deltaX = deltas[0];
            var deltaY = deltas[1];

            // Grid with extended outer grid-area non-visible            
            var a = MathTool.FloorLatLon(jsonReceive.Viewport.Minx, deltaX) - deltaX * AlgoConfig.OuterGridExtend;
            var b = MathTool.FloorLatLon(jsonReceive.Viewport.Miny, deltaY) - deltaY * AlgoConfig.OuterGridExtend;
            var a2 = MathTool.FloorLatLon(jsonReceive.Viewport.Maxx, deltaX) + deltaX * (1 + AlgoConfig.OuterGridExtend);
            var b2 = MathTool.FloorLatLon(jsonReceive.Viewport.Maxy, deltaY) + deltaY * (1 + AlgoConfig.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 static double[] GetDelta(JsonGetMarkersReceive jsonReceive)
        {
            // Heuristic specific values and grid size dependent.
            // used in combination with zoom level.

            // xZoomLevel1 and yZoomLevel1 is used to define the size of one grid-cell

            // Absolute base value of longitude distance
            const int xZoomLevel1 = 480;
            // Absolute base value of latitude distance
            const int yZoomLevel1 = 240;

            // Relative values, used for adjusting grid size
            var gridScaleX = AlgoConfig.Gridx;
            var gridScaleY = AlgoConfig.Gridy;

            var x = MathTool.Half(xZoomLevel1, jsonReceive.Zoomlevel - 1) / gridScaleX;
            var y = MathTool.Half(yZoomLevel1, jsonReceive.Zoomlevel - 1) / gridScaleY;
            return new double[] { x, y };
        }
        void MakeLines(JsonGetMarkersReceive jsonReceive)
        {
            if(!jsonReceive.IsDebugLinesEnabled) return; // 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;
            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)
                {
                    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)
                {
                    // 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 });
                }
            }

            // 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 });
            }
        }
        // Post
        public JsonMarkersReply Markers(
            double nelat, double nelon, double swlat, double swlon,
            int zoomlevel, string filter, int sendid
            )
        {
            var sw = new Stopwatch();
            sw.Start();

            var jsonReceive = new JsonGetMarkersReceive(nelat, nelon, swlat, swlon, zoomlevel, filter, sendid);

            var clusteringEnabled = jsonReceive.IsClusteringEnabled || AlgoConfig.AlwaysClusteringEnabledWhenZoomLevelLess > jsonReceive.Zoomlevel;

            JsonMarkersReply reply;

            jsonReceive.Viewport.ValidateLatLon(); // Validate google map viewport input (is always valid)
            jsonReceive.Viewport.Normalize();

            // Get all points from memory
            IPoints points = MemoryDatabase.GetPoints();

            if (jsonReceive.TypeFilterExclude.Count == AlgoConfig.MarkerTypes.Count)
            {
                // Filter all 
                points = new Points(); // empty
            }
            else if (jsonReceive.TypeFilterExclude.Count > 0)
            {
                // Filter data by typeFilter value
                // Make new obj, don't overwrite obj data
                points = new Points
                              {
                                  Data = points.Data
                                  .Where(p => jsonReceive.TypeFilterExclude.Contains(p.T) == false)
                                  .ToList()
                              };
            }

            // Create new instance for every ajax request with input all points and json data
            var clusterAlgo = new GridCluster(points, jsonReceive); // create polylines
                     
            // Clustering
            if (clusteringEnabled && jsonReceive.Zoomlevel < AlgoConfig.ZoomlevelClusterStop)
            {
                // Calculate data to be displayed
                var clusterPoints = clusterAlgo.GetCluster(new ClusterInfo
                                                               {
                                                                   ZoomLevel = jsonReceive.Zoomlevel,                                                                   
                                                               });                
                                
                var converted = DataConvert(clusterPoints);
                                
                // Prepare data to the client
                reply = new JsonMarkersReply
                            {
                                Markers = converted,
                                Rid = sendid,
                                Polylines = clusterAlgo.Lines,
                                Msec = Sw(sw),
                            };

                // Return client data
                return reply;
            }

            // If we are here then there are no clustering
            // The number of items returned is restricted to avoid json data overflow
            IPoints filteredDataset = ClusterAlgorithmBase.FilterDataset(points, jsonReceive.Viewport);
            IPoints filteredDatasetMaxPoints = new Points
                                                   {
                                                       Data = filteredDataset.Data
                                                       .Take(AlgoConfig.MaxMarkersReturned)
                                                       .ToList()
                                                   };

            reply = new JsonMarkersReply
                        {
                            Markers = DataConvert(filteredDatasetMaxPoints),
                            Rid = sendid,
                            Polylines = clusterAlgo.Lines,
                            Mia = filteredDataset.Count - filteredDatasetMaxPoints.Count,
                            Msec = Sw(sw),
                        };
            return reply;
        }