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 }; }
public static bool IsInside(Boundary b, MapPoint p) { return IsInside(b.MinX, b.MinY, b.MaxX, b.MaxY, p.X, p.Y, false, false); }