/// <summary>
 /// todo use threads and threadData
 /// </summary>
 /// <param name="points"></param>
 /// <param name="input"></param>
 public GridCluster(IList<MapPoint> points, MarkersInput input)
     : base(points)
 {
     this._input = 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(MarkersInput 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;
        }
        /// <summary>
        /// Read Through Cache
        /// </summary>
        /// <param name="getParams"></param>
        /// <returns></returns>
        private ClusterMarkersResponse GetMarkersHelper(GetMarkersParams getParams)
        {
            try
            {
                var neLat = getParams.NorthEastLatitude;
                var neLong = getParams.NorthEastLongitude;
                var swLat = getParams.SouthWestLatitude; 
                var swLong = getParams.SouthWestLongitude;
                var zoomLevel = getParams.ZoomLevel;
                var filter = getParams.Filter ?? "";

                // values are validated there
                var markersInput = new MarkersInput(neLat, neLong, swLat, swLong, zoomLevel, filter);

                var grid = GridCluster.GetBoundaryExtended(markersInput);
                var cacheKeyHelper = string.Format("{0}_{1}_{2}", markersInput.Zoomlevel, markersInput.FilterHashCode(), grid.GetHashCode());
                var cacheKey = CacheKeys.GetMarkers(cacheKeyHelper.GetHashCode());

                var response = new ClusterMarkersResponse();

                markersInput.Viewport.ValidateLatLon(); // Validate google map viewport input (should be always valid)
                markersInput.Viewport.Normalize();

                // Get all points from memory
                IList<MapPoint> points = _pointCollection.Get(getParams.PointType); // _pointsDatabase.GetPoints();

                // Filter points
                points = FilterUtil.FilterByType(
                    points,
                    new FilterData { TypeFilterExclude = markersInput.TypeFilterExclude }
                );



                // Create new instance for every ajax request with input all points and json data
                var clusterAlgo = new GridCluster(points, markersInput);

                var clusteringEnabled = markersInput.IsClusteringEnabled
                    || GmcSettings.Get.AlwaysClusteringEnabledWhenZoomLevelLess > markersInput.Zoomlevel;

                // Clustering
                if (clusteringEnabled && markersInput.Zoomlevel < GmcSettings.Get.ZoomlevelClusterStop)
                {
                    IList<MapPoint> markers = clusterAlgo.RunCluster();

                    response = new ClusterMarkersResponse
                    {
                        Markers = markers,
                        Polylines = clusterAlgo.GetPolyLines()
                    };
                }
                else
                {
                    // If we are here then there are no clustering
                    // The number of items returned is restricted to avoid json data overflow
                    IList<MapPoint> filteredDataset = FilterUtil.FilterDataByViewport(points, markersInput.Viewport);
                    IList<MapPoint> filteredDatasetMaxPoints = filteredDataset.Take(GmcSettings.Get.MaxMarkersReturned).ToList();

                    response = new ClusterMarkersResponse
                    {
                        Markers = filteredDatasetMaxPoints,
                        Polylines = clusterAlgo.GetPolyLines(),
                        Mia = filteredDataset.Count - filteredDatasetMaxPoints.Count,
                    };
                }
                
                // if client ne and sw is inside a specific grid box then cache the grid box and the result
                // next time test if ne and sw is inside the grid box and return the cached result
                if (GmcSettings.Get.CacheServices)
                    _memCache.Set(cacheKey, response, TimeSpan.FromMinutes(10)); // cache data

                return response;
            }
            catch (Exception ex)
            {
                return new ClusterMarkersResponse
                {
                    OperationResult = "0",
                    ErrorMessage = string.Format("MapService says: exception {0}", ex.Message)
                };
            }
        }