/// <summary> /// Clusters the specified items. /// </summary> /// <param name="itemsToBeClustered">The items to be clustered.</param> /// <returns>The set of clustered map items.</returns> public static IEnumerable<MapItem> Cluster(IEnumerable<MapItem> itemsToBeClustered) { // Add all of the items to a map item quad tree. MapItemQuadTree tree = new MapItemQuadTree(); foreach (MapItem item in itemsToBeClustered) { tree.Add(item); } Dictionary<int, List<MapItemQuadTreeNode>> nodesByZoomLevel = GroupNodesByZoomLevel(tree); // Sort all of the zoom levels present in descending order. List<int> zoomLevels = new List<int>(nodesByZoomLevel.Keys); zoomLevels.Sort(); zoomLevels.Reverse(); // Iterate over all zoom levels from finest to coarsest... foreach (int zoomLevel in zoomLevels) { // ...and cluster each node, one by one. foreach (var node in nodesByZoomLevel[zoomLevel]) { ClusterNodeItems(tree, node); } } // A single map item may appear in multiple nodes, so take care not to return duplicates. HashSet<MapItem> items = new HashSet<MapItem>(); foreach (var node in tree.Nodes) { foreach (var item in node.Items) { items.Add(item); } } return items; }
/// <summary> /// Recursively adds all items that intersect the specified item to the clump. /// </summary> private static void GrowClump(MapItemQuadTree tree, MapItem item, int zoomLevel, HashSet<MapItem> visitedNodes, List<MapItem> clump) { if (visitedNodes.Add(item)) { clump.Add(item); List<MapItem> intersectingItems = new List<MapItem>(tree.IntersectingItems(item.BoundingRectAtZoomLevel(zoomLevel), zoomLevel)); foreach (var intersectingItem in intersectingItems) { GrowClump(tree, intersectingItem, zoomLevel, visitedNodes, clump); } } }
/// <summary> /// Clusters the map items contained in the node. Clustered items are modified and new items are added /// to the tree to represent the clusters. /// </summary> private static void ClusterNodeItems(MapItemQuadTree tree, MapItemQuadTreeNode node) { var clumps = new List<List<MapItem>>(); var clusters = new List<Tuple<List<MapItem>, Point>>(); HashSet<MapItem> visitedNodes = new HashSet<MapItem>(); // Create the set of clusters of intersecting items. foreach (var item in node.Items) { List<MapItem> clump = new List<MapItem>(); GrowClump(tree, item, node.ZoomLevel, visitedNodes, clump); if (clump.Count > 1) { foreach (var cluster in ClusterClump(clump, node.ZoomLevel)) { if (cluster.Item1.Count > 1) { clusters.Add(cluster); } } } } // For each cluster, modify the existing items in the cluster such that they don't appear at the same // LOD as the cluster and add new item(s) to the tree that represent the cluster. foreach (var cluster in clusters) { Debug.Assert(cluster.Item1.Count > 1); Point clusterLocation = cluster.Item2; var clusterItem = new FixedSizeInScreenSpaceMapItem( clusterLocation.ToLocation(), PositionOrigin.Center, new Size(20, 20), 0, node.ZoomLevel); tree.Add(clusterItem); foreach (var item in cluster.Item1) { tree.Remove(item); // If this item isn't present at a finer LOD than this, then it's been totally // subsumed by the cluster; otherwise, re-add it to the tree with adjusted // min zoom level. if (item.MaxZoomLevel > node.ZoomLevel) { var adjustedItem = new FixedSizeInScreenSpaceMapItem( item.Location, PositionOrigin.Center, new Size(20, 20), node.ZoomLevel + 1, item.MaxZoomLevel) { Parent = clusterItem }; // We're removing the item, so re-assign the children's parent to the cluster that's // standing in for it. foreach (var child in item.Children) { child.Parent = adjustedItem; adjustedItem.AddChild(child); } clusterItem.AddChild(adjustedItem); tree.Add(adjustedItem); } } } }
/// <summary> /// Iterates over all nodes in the quad tree, grouping them by zoom level. /// </summary> /// <param name="tree">The tree.</param> /// <returns></returns> private static Dictionary<int, List<MapItemQuadTreeNode>> GroupNodesByZoomLevel(MapItemQuadTree tree) { Dictionary<int, List<MapItemQuadTreeNode>> nodesByZoomLevel; nodesByZoomLevel = new Dictionary<int, List<MapItemQuadTreeNode>>(); foreach (var node in tree.Nodes) { List<MapItemQuadTreeNode> nodesAtZoomLevel; nodesByZoomLevel.TryGetValue(node.ZoomLevel, out nodesAtZoomLevel); if (nodesAtZoomLevel == null) { nodesAtZoomLevel = nodesByZoomLevel[node.ZoomLevel] = new List<MapItemQuadTreeNode>(); } nodesAtZoomLevel.Add(node); } return nodesByZoomLevel; }
/// <summary> /// Initializes a new instance of the <see cref="QuadTreeMapItemSet"/> class. /// </summary> public QuadTreeMapItemSet() { _Items = new MapItemQuadTree(); _VisibleItems = new HashSet<MapItem>(); _NewVisibleItems = new HashSet<MapItem>(); }