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

            // 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.Count = 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<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;
        }
        public MarkersInput(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;
        }
 // O(n)
 public static IList<MapPoint> FilterDataByViewport(IList<MapPoint> points, Boundary viewport)
 {
     return points
          .Where(i => MathTool.IsInside(viewport, i))
          .ToList();
 }
        /// <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 (!_input.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 (_input.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 = LatLongInfo.MinLatValue + restrictLat, MaxX = xx, MaxY = 0 });
                    temp.Add(new Rectangle { MinX = xx, MinY = 0, MaxX = xx, MaxY = LatLongInfo.MaxLatValue - restrictLat });
                }
            }

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

                // Draw region
                if (_input.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 = LatLongInfo.MinLonValue, MinY = yy, MaxX = 0, MaxY = yy });
                    temp.Add(new Rectangle { MinX = 0, MinY = yy, MaxX = LatLongInfo.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(MapPoint point, 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 = point.Y - grid.MinY;

            var overlapMapMinX = (int)(LatLongInfo.MinLonValue / deltaX) - 1;
            var overlapMapMaxX = (int)(LatLongInfo.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(LatLongInfo.MaxLonValue % deltaX - 0) < Numbers.Epsilon)
            {
                overlapMapMaxX--;
                overlapMapMinX++;
            }

            var idxx = (int)(point.X / deltaX);
            if (point.X < 0) idxx--;

            if (Math.Abs(LatLongInfo.MaxLonValue % point.X - 0) < Numbers.Epsilon)
            {
                if (point.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 };
        }
예제 #8
0
 public static bool IsInside(Boundary b, MapPoint p)
 {
     return IsInside(b.MinX, b.MinY, b.MaxX, b.MaxY, p.X, p.Y, false, false);
 }