const double Exp = 2; // 2=euclid, 1=manhatten // Minkowski dist // if lat lon precise dist is needed, use Haversine or similar formulas // this is approx calc for clustering, no precise dist is needed public static double Distance(MapPoint a, MapPoint b) { // lat lon wrap, values don't seem needed to be normalized to [0;1] for better distance calc var absx = LatLonDiff(a.X, b.X); var absy = LatLonDiff(a.Y, b.Y); return Math.Pow(Math.Pow(absx, Exp) + Math.Pow(Math.Abs(absy), Exp), 1.0 / Exp); }
public int CompareTo(MapPoint other, int dimension) { switch (dimension) { case 0: return this.X.CompareTo(other.X); case 1: return this.Y.CompareTo(other.Y); default: throw new ArgumentException("Invalid dimension."); } }
public void BuildContent(MapPoint point) { if (point == null) { Content = "Marker could not be found"; return; } Id = point.MarkerId.ToString(); Type = point.MarkerType; Lat = point.Lat; Long = point.Long; var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("<div class='gmcKN-marker-info'>"); stringBuilder.AppendFormat("Time: {0}<br/>",DateTime.Now.ToString("HH:mm:ss")); stringBuilder.AppendFormat("Id: {0}<br /> Type: {1}<br />", Id, Type); stringBuilder.AppendFormat("Lat: {0} Lon: {1}", point.Lat, point.Long); stringBuilder.AppendLine("</div>"); Content = stringBuilder.ToString(); }
// 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 double Haversine(MapPoint p1, MapPoint p2) { return Haversine(p1.Y, p1.X, p2.Y, p2.X); }
public static bool IsInside(Boundary b, MapPoint p) { return IsInside(b.MinX, b.MinY, b.MaxX, b.MaxY, p.X, p.Y, false, false); }
// Circular mean, very relevant for points around New Zealand, where lon -180 to 180 overlap // Adapted Centroid Calculation of N Points for Google Maps usage // If you are working with points in a specific country only such as India or USA then a simpler centroid calc can be used // http://en.wikipedia.org/wiki/Centroid public static MapPoint GetCentroidFromClusterLatLon(List<MapPoint> list) //O(n) { int count; if (list == null || (count = list.Count) == 0) return null; if (count == 1) { return list.First(); } // http://en.wikipedia.org/wiki/Circular_mean // http://stackoverflow.com/questions/491738/how-do-you-calculate-the-average-of-a-set-of-angles /* 1/N* sum_i_from_1_to_N sin(a[i]) a = atan2 --------------------------- 1/N* sum_i_from_1_to_N cos(a[i]) */ double longSin = 0; double longCos = 0; double latSin = 0; double latCos = 0; foreach (var p in list) { longSin += Math.Sin(p.X.LatLongToRadian()); longCos += Math.Cos(p.X.LatLongToRadian()); latSin += Math.Sin(p.Y.LatLongToRadian()); latCos += Math.Cos(p.Y.LatLongToRadian()); } longSin /= count; longCos /= count; double radx = 0; double rady = 0; // if both are zero-ish if (Math.Abs(longSin - 0) > Numbers.Epsilon && Math.Abs(longCos - 0) > Numbers.Epsilon) { radx = Math.Atan2(longSin, longCos); rady = Math.Atan2(latSin, latCos); } var x = radx.RadianToLatLong(); var y = rady.RadianToLatLong(); var centroid = new MapPoint { X = x, Y = y, Count = count }; return centroid; }
public MapPoint GetClosestPoint(MapPoint from, List<MapPoint> list) // O(n) { var min = double.MaxValue; MapPoint closests = null; foreach (var p in list) { var dist = MathTool.Distance(from, p); if (dist >= min) continue; // update min = dist; closests = p; } return closests; }