public Boundary(Boundary b) { this.Minx = b.Minx; this.Miny = b.Miny; this.Maxx = b.Maxx; this.Maxy = b.Maxy; }
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); }
static IPoints GenerateRandomDataset(int n, Boundary b) { b.Normalize(); // Random points var list = new List<IP>(); for (var i = 0; i < n; i++) { var lat = (b.Miny + b.AbsY * Rand.NextDouble()).NormalizeLatitude(); var lon = (b.Minx + b.AbsX * Rand.NextDouble()).NormalizeLongitude(); list.Add(new P { I = Rand.Next(1000000000), C = 1, Y = lat, X = lon, T = i }); } return new Points {Data = list}; }
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; }
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 }); } }
// To work properly it requires the p is already normalized public static int[] GetPointMappedIds(IP p, Boundary grid, double deltax, double deltay) { var relativeX = p.X - grid.Minx; var relativeY = p.Y - grid.Miny; int idx, idy; // Naive version, lon points near 180 and lat points near 90 are not clustered together //idx = (int)(relativeX / deltax); //idy = (int)(relativeY / deltay); // end Naive version /* 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 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; idx = idxx; // Latitude never wraps around with Google Maps, ignore 90, -90 wrap-around for latitude idy = (int)(relativeY / deltay); return new[] { idx, idy }; }
public static bool IsInside(Boundary b, IP p) { return IsInside(b.Minx, b.Miny, b.Maxx, b.Maxy, p.X, p.Y, false, false); }
// O(n), could be O(logn-ish) using range search or similar, no problem when points are <500.000 public static IPoints FilterDataset(IPoints dataset, Boundary viewport) { //return new Points { Data = dataset.Data.Where(i => MathTool.IsInsideWiden(viewport, i)).ToList() }; return new Points {Data = dataset.Data.Where(i => MathTool.IsInside(viewport, i)).ToList()}; }
public IPoints GetClusterResult(Boundary grid) { // Collect used buckets and return the result var clusterPoints = new Points(); //O(m*n) foreach (var item in BucketsLookup) { var bucket = item.Value; if (!bucket.IsUsed) continue; if (bucket.Points.Count < AlgoConfig.MinClusterSize) clusterPoints.Data.AddRange(bucket.Points.Data); 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 }
static void ReadSerializedFileAndCount() { var points = FileUtil.LoadDataSetFromFile(); var b = new Boundary { Minx = -179, Maxx = 179, Miny = -90, Maxy = 90 }; int i = points.Data.Count(p => MathTool.IsInside(b, p)); CWF("count: {0}", i); }
static void PMapTest() { var b = new Boundary { Minx = -180, Maxx = 180, Miny = -90, Maxy = 90 }; const int dx = 20; const int dy = 20; var xy = GridCluster.GetPointMappedIds(new P { X = 175, Y = 35 }, b, dx, dy); CWF("x: {0} y: {1}", xy[0], xy[1]); xy = GridCluster.GetPointMappedIds(new P { X = 175, Y = 35 }, b, dx, dy); CWF("x: {0} y: {1}", xy[0], xy[1]); xy = GridCluster.GetPointMappedIds(new P { X = 180, Y = 35 }, b, dx, dy); CWF("x: {0} y: {1}", xy[0], xy[1]); xy = GridCluster.GetPointMappedIds((new P { X = 181, Y = 35 }).Normalize(), b, dx, dy); CWF("x: {0} y: {1}", xy[0], xy[1]); xy = GridCluster.GetPointMappedIds((new P { X = -181, Y = 35 }).Normalize(), b, dx, dy); CWF("x: {0} y: {1}", xy[0], xy[1]); }