public static MapCluster RootClusterForAnnotations(List <MapPointAnnotation> initialAnnotations, double gamma, string clusterTitle, bool showSubtitle) { // KDTree var boundaries = new MKMapRect(Single.PositiveInfinity, Single.PositiveInfinity, 0.0, 0.0); foreach (var point in initialAnnotations.Select(annotation => annotation.MapPoint)) { if (point.X < boundaries.Origin.X) { boundaries.Origin.X = point.X; } if (point.Y < boundaries.Origin.Y) { boundaries.Origin.Y = point.Y; } if (point.X > boundaries.Origin.X + boundaries.Size.Width) { boundaries.Size.Width = point.X - boundaries.Origin.X; } if (point.Y > boundaries.Origin.Y + boundaries.Size.Height) { boundaries.Size.Height = point.Y - boundaries.Origin.Y; } } Console.WriteLine("Computing KD-tree..."); var cluster = new MapCluster(initialAnnotations, 0, boundaries, gamma, clusterTitle, showSubtitle); Console.WriteLine("Computation done !"); return(cluster); }
public ClusterAnnotation() { _cluster = null; Coordinate = MapConstants.Offscreen; Type = ClusterAnnotationType.Unknown; ShouldBeRemovedAfterAnimation = false; }
public bool IsAncestorOf(MapCluster mapCluster) { return(Depth < mapCluster.Depth && (_leftChild == mapCluster || _rightChild == mapCluster || _leftChild.IsAncestorOf(mapCluster) || _rightChild.IsAncestorOf(mapCluster))); }
public MapCluster(List <MapPointAnnotation> annotations, int depth, MKMapRect mapRect, double gamma, string clusterTitle, bool showSubtitle) { Depth = depth; _mapRect = mapRect; _clusterTitle = clusterTitle; ShowSubtitle = showSubtitle; switch (annotations.Count) { case 0: _leftChild = null; _rightChild = null; Annotation = null; ClusterCoordinate = new CLLocationCoordinate2D(91, 181); // Todo: Invalid, instead of constant. break; case 1: _leftChild = null; _rightChild = null; Annotation = annotations.Last(); ClusterCoordinate = Annotation.Annotation.Coordinate; break; default: { Annotation = null; // Principal Component Analysis // If cov(x,y) = ∑(x-x_mean) * (y-y_mean) != 0 (covariance different from zero), we are looking for the following principal vector: // a (aX) // (aY) // // x_ = x - x_mean ; y_ = y - y_mean // // aX = cov(x_,y_) // // // aY = 0.5/n * ( ∑(x_^2) + ∑(y_^2) + sqrt( (∑(x_^2) + ∑(y_^2))^2 + 4 * cov(x_,y_)^2 ) ) // compute the means of the coordinate var xSum = 0.0; var ySum = 0.0; foreach (var annotation in annotations) { xSum += annotation.MapPoint.X; ySum += annotation.MapPoint.Y; } var xMean = xSum / annotations.Count; var yMean = ySum / annotations.Count; if (gamma != 1.0) { // take gamma weight into account var gammaSumX = 0.0; var gammaSumY = 0.0; var maxDistance = 0.0; var meanCenter = new MKMapPoint(xMean, yMean); foreach (var annotation in annotations) { var distance = MKGeometry.MetersBetweenMapPoints(annotation.MapPoint, meanCenter); if (distance > maxDistance) { maxDistance = distance; } } var totalWeight = 0.0; foreach (var annotation in annotations) { var point = annotation.MapPoint; var distance = MKGeometry.MetersBetweenMapPoints(point, meanCenter); var normalizedDistance = maxDistance != 0.0 ? distance / maxDistance : 1.0; var weight = Math.Pow(normalizedDistance, gamma - 1.0); gammaSumX += point.X * weight; gammaSumY += point.Y * weight; totalWeight += weight; } xMean = gammaSumX / totalWeight; yMean = gammaSumY / totalWeight; } // compute coefficients var sumXsquared = 0.0; var sumYsquared = 0.0; var sumXy = 0.0; foreach (var annotation in annotations) { var x = annotation.MapPoint.X - xMean; var y = annotation.MapPoint.Y - yMean; sumXsquared += x * x; sumYsquared += y * y; sumXy += x * y; } double aX; double aY; if (Math.Abs(sumXy) / annotations.Count > MapConstants.ClusterDiscriminationPrecision) { aX = sumXy; var lambda = 0.5 * ((sumXsquared + sumYsquared) + Math.Sqrt((sumXsquared + sumYsquared) * (sumXsquared + sumYsquared) + 4 * sumXy * sumXy)); aY = lambda - sumXsquared; } else { aX = sumXsquared > sumYsquared ? 1.0 : 0.0; aY = sumXsquared > sumYsquared ? 0.0 : 1.0; } List <MapPointAnnotation> leftAnnotations; List <MapPointAnnotation> rightAnnotations; if (Math.Abs(sumXsquared) / annotations.Count < MapConstants.ClusterDiscriminationPrecision || Math.Abs(sumYsquared) / annotations.Count < MapConstants.ClusterDiscriminationPrecision) { // then every x equals XMean and we have to arbitrarily choose where to put the pivotIndex var pivotIndex = annotations.Count / 2; leftAnnotations = annotations.GetRange(0, pivotIndex); rightAnnotations = annotations.GetRange(pivotIndex, annotations.Count - pivotIndex); } else { // compute scalar product between the vector of this regression line and the vector // (x - x(mean)) // (y - y(mean)) // the sign of this scalar product determines which cluster the point belongs to leftAnnotations = new List <MapPointAnnotation>(); rightAnnotations = new List <MapPointAnnotation>(); foreach (var annotation in annotations) { var point = annotation.MapPoint; var positivityConditionOfScalarProduct = true; // TODO: Don't know why this is here... its there in the original code. Or is it a wrong Objective-C interpretation? if (true) { positivityConditionOfScalarProduct = (point.X - xMean) * aX + (point.Y - yMean) * aY > 0.0; } else { positivityConditionOfScalarProduct = (point.Y - yMean) > 0.0; } if (positivityConditionOfScalarProduct) { leftAnnotations.Add(annotation); } else { rightAnnotations.Add(annotation); } } } // compute map rects double xMin = float.MaxValue, xMax = 0.0f, yMin = float.MaxValue, yMax = 0.0f; foreach (var point in leftAnnotations.Select(annotation => annotation.MapPoint)) { if (point.X > xMax) { xMax = point.X; } if (point.Y > yMax) { yMax = point.Y; } if (point.X < xMin) { xMin = point.X; } if (point.Y < yMin) { yMin = point.Y; } } var leftMapRect = new MKMapRect(xMin, yMin, xMax - xMin, yMax - yMin); xMin = float.MaxValue; xMax = 0.0; yMin = float.MaxValue; yMax = 0.0; foreach (var point in rightAnnotations.Select(annotation => annotation.MapPoint)) { if (point.X > xMax) { xMax = point.X; } if (point.Y > yMax) { yMax = point.Y; } if (point.X < xMin) { xMin = point.X; } if (point.Y < yMin) { yMin = point.Y; } } var rightMapRect = new MKMapRect(xMin, yMin, xMax - xMin, yMax - yMin); ClusterCoordinate = MKMapPoint.ToCoordinate(new MKMapPoint(xMean, yMean)); _leftChild = new MapCluster(leftAnnotations, depth + 1, leftMapRect, gamma, clusterTitle, showSubtitle); _rightChild = new MapCluster(rightAnnotations, depth + 1, rightMapRect, gamma, clusterTitle, showSubtitle); } break; } }
public void Reset() { Cluster = null; Coordinate = MapConstants.Offscreen; }
public void SetAnnotations(List <MKAnnotation> annotations) { if (!_isSettingAnnotations) { _originalAnnotations = annotations; _isSettingAnnotations = true; RemoveAnnotations(_clusterAnnotations.ToArray()); // TODO: Co-variant conversion _singleAnnotationsPool = new List <ClusterAnnotation>(); _clusterAnnotationsPool = new List <ClusterAnnotation>(); int numberOfAnnotationsInPool = 2 * NumberOfClusters(); for (int i = 0; i < numberOfAnnotationsInPool; i++) { var annotation = new ClusterAnnotation { Type = ClusterAnnotationType.Leaf }; _singleAnnotationsPool.Add(annotation); annotation = new ClusterAnnotation { Type = ClusterAnnotationType.Cluster }; _clusterAnnotationsPool.Add(annotation); } AddAnnotations(_singleAnnotationsPool.ToArray()); // TODO: Co-variant conversion AddAnnotations(_clusterAnnotationsPool.ToArray()); // TODO: Co-variant conversion var temp = _singleAnnotationsPool; temp.AddRange(_clusterAnnotationsPool); _clusterAnnotations = temp; const double gamma = 1.0; const string clusterTitle = "%d elements"; // TODO: Run async, like the original, performance optimizing //await Task.Run(async () => //{ var mapPointAnnotations = new List <MapPointAnnotation>(); foreach (var annotation in annotations) { var mapPointAnnotation = new MapPointAnnotation(annotation); mapPointAnnotations.Add(mapPointAnnotation); } const bool shouldShowSubtitle = true; _rootMapCluster = MapCluster.RootClusterForAnnotations(mapPointAnnotations, gamma, clusterTitle, shouldShowSubtitle); // TODO: When implementing async; run on main thread from here, uses interface objects. ClusterInMapRect(VisibleMapRect); _isSettingAnnotations = false; if (_annotationsToBeSet != null) { var newAnnotations = _annotationsToBeSet; _annotationsToBeSet = null; SetAnnotations(newAnnotations); } //}); } else { _annotationsToBeSet = annotations; } }