namespace GMap.NET.WindowsPresentation { using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Windows; #if DEBUG_DUMP using System.Windows.Controls; using System.Windows.Shapes; using System.Windows.Media; using System.Xml; #endif /// /// This class efficiently stores and retrieves arbitrarily sized and positioned /// objects in a quad-tree data structure. This can be used to do efficient hit /// detection or visiblility checks on objects in a virtualized canvas. /// The object does not need to implement any special interface because the Rect Bounds /// of those objects is handled as a separate argument to Insert. /// public class QuadTree where T : class { Rect _bounds; // overall bounds we are indexing. Quadrant _root; IDictionary _table; /// /// Each node stored in the tree has a position, width & height. /// internal class QuadNode { Rect _bounds; QuadNode _next; // linked in a circular list. T _node; // the actual visual object being stored here. /// /// Construct new QuadNode to wrap the given node with given bounds /// /// The node /// The bounds of that node public QuadNode(T node, Rect bounds) { _node = node; _bounds = bounds; } /// /// The node /// public T Node { get { return _node; } set { _node = value; } } /// /// The Rect bounds of the node /// public Rect Bounds { get { return _bounds; } } /// /// QuadNodes form a linked list in the Quadrant. /// public QuadNode Next { get { return _next; } set { _next = value; } } } /// /// The canvas is split up into four Quadrants and objects are stored in the quadrant that contains them /// and each quadrant is split up into four child Quadrants recurrsively. Objects that overlap more than /// one quadrant are stored in the _nodes list for this Quadrant. /// internal class Quadrant { Quadrant _parent; Rect _bounds; // quadrant bounds. QuadNode _nodes; // nodes that overlap the sub quadrant boundaries. // The quadrant is subdivided when nodes are inserted that are // completely contained within those subdivisions. Quadrant _topLeft; Quadrant _topRight; Quadrant _bottomLeft; Quadrant _bottomRight; #if DEBUG_DUMP public void ShowQuadTree(Canvas c) { Rectangle r = new Rectangle(); r.Width = _bounds.Width; r.Height = _bounds.Height; Canvas.SetLeft(r, _bounds.Left); Canvas.SetTop(r, _bounds.Top); r.Stroke = Brushes.DarkRed; r.StrokeThickness = 1; r.StrokeDashArray = new DoubleCollection(new double[] { 2.0, 3.0 }); c.Children.Add(r); if (_topLeft != null) _topLeft.ShowQuadTree(c); if (_topRight != null) _topRight.ShowQuadTree(c); if (_bottomLeft != null) _bottomLeft.ShowQuadTree(c); if (_bottomRight != null) _bottomRight.ShowQuadTree(c); } public void Dump(LogWriter w) { w.WriteAttribute("Bounds", _bounds.ToString()); if (_nodes != null) { QuadNode n = _nodes; do { n = n.Next; // first node. w.Open("node"); w.WriteAttribute("Bounds", n.Bounds.ToString()); w.Close(); } while (n != _nodes); } DumpQuadrant("TopLeft", _topLeft, w); DumpQuadrant("TopRight", _topRight, w); DumpQuadrant("BottomLeft", _bottomLeft, w); DumpQuadrant("BottomRight", _bottomRight, w); } public void DumpQuadrant(string label, Quadrant q, LogWriter w) { if (q != null) { w.Open("Quadrant"); w.WriteAttribute("Name", label); q.Dump(w); w.Close(); } } #endif /// /// Construct new Quadrant with a given bounds all nodes stored inside this quadrant /// will fit inside this bounds. /// /// The parent quadrant (if any) /// The bounds of this quadrant public Quadrant(Quadrant parent, Rect bounds) { _parent = parent; Debug.Assert(bounds.Width != 0 && bounds.Height != 0); if(bounds.Width == 0 || bounds.Height == 0) { // todo: localize throw new ArgumentException("Bounds of quadrant cannot be zero width or height"); } _bounds = bounds; } /// /// The parent Quadrant or null if this is the root /// internal Quadrant Parent { get { return _parent; } } /// /// The bounds of this quadrant /// internal Rect Bounds { get { return _bounds; } } /// /// Insert the given node /// /// The node /// The bounds of that node /// internal Quadrant Insert(T node, Rect bounds) { Debug.Assert(bounds.Width != 0 && bounds.Height != 0); if(bounds.Width == 0 || bounds.Height == 0) { // todo: localize throw new ArgumentException("Bounds of quadrant cannot be zero width or height"); } double w = _bounds.Width / 2; if(w == 0) { w = 1; } double h = _bounds.Height / 2; if(h == 0) { h = 1; } // assumption that the Rect struct is almost as fast as doing the operations // manually since Rect is a value type. Rect topLeft = new Rect(_bounds.Left, _bounds.Top, w, h); Rect topRight = new Rect(_bounds.Left + w, _bounds.Top, w, h); Rect bottomLeft = new Rect(_bounds.Left, _bounds.Top + h, w, h); Rect bottomRight = new Rect(_bounds.Left + w, _bounds.Top + h, w, h); Quadrant child = null; // See if any child quadrants completely contain this node. if(topLeft.Contains(bounds)) { if(_topLeft == null) { _topLeft = new Quadrant(this, topLeft); } child = _topLeft; } else if(topRight.Contains(bounds)) { if(_topRight == null) { _topRight = new Quadrant(this, topRight); } child = _topRight; } else if(bottomLeft.Contains(bounds)) { if(_bottomLeft == null) { _bottomLeft = new Quadrant(this, bottomLeft); } child = _bottomLeft; } else if(bottomRight.Contains(bounds)) { if(_bottomRight == null) { _bottomRight = new Quadrant(this, bottomRight); } child = _bottomRight; } if(child != null) { return child.Insert(node, bounds); } else { QuadNode n = new QuadNode(node, bounds); if(_nodes == null) { n.Next = n; } else { // link up in circular link list. QuadNode x = _nodes; n.Next = x.Next; x.Next = n; } _nodes = n; return this; } } /// /// Returns all nodes in this quadrant that intersect the given bounds. /// The nodes are returned in pretty much random order as far as the caller is concerned. /// /// List of nodes found in the given bounds /// The bounds that contains the nodes you want returned internal void GetIntersectingNodes(List nodes, Rect bounds) { if(bounds.IsEmpty) return; double w = _bounds.Width / 2; double h = _bounds.Height / 2; // assumption that the Rect struct is almost as fast as doing the operations // manually since Rect is a value type. Rect topLeft = new Rect(_bounds.Left, _bounds.Top, w, h); Rect topRight = new Rect(_bounds.Left + w, _bounds.Top, w, h); Rect bottomLeft = new Rect(_bounds.Left, _bounds.Top + h, w, h); Rect bottomRight = new Rect(_bounds.Left + w, _bounds.Top + h, w, h); // See if any child quadrants completely contain this node. if(topLeft.IntersectsWith(bounds) && _topLeft != null) { _topLeft.GetIntersectingNodes(nodes, bounds); } if(topRight.IntersectsWith(bounds) && _topRight != null) { _topRight.GetIntersectingNodes(nodes, bounds); } if(bottomLeft.IntersectsWith(bounds) && _bottomLeft != null) { _bottomLeft.GetIntersectingNodes(nodes, bounds); } if(bottomRight.IntersectsWith(bounds) && _bottomRight != null) { _bottomRight.GetIntersectingNodes(nodes, bounds); } GetIntersectingNodes(_nodes, nodes, bounds); } /// /// Walk the given linked list of QuadNodes and check them against the given bounds. /// Add all nodes that intersect the bounds in to the list. /// /// The last QuadNode in a circularly linked list /// The resulting nodes are added to this list /// The bounds to test against each node static void GetIntersectingNodes(QuadNode last, List nodes, Rect bounds) { if(last != null) { QuadNode n = last; do { n = n.Next; // first node. if(n.Bounds.IntersectsWith(bounds)) { nodes.Add(n); } } while(n != last); } } /// /// Return true if there are any nodes in this Quadrant that intersect the given bounds. /// /// The bounds to test /// boolean internal bool HasIntersectingNodes(Rect bounds) { if(bounds.IsEmpty) return false; double w = _bounds.Width / 2; double h = _bounds.Height / 2; // assumption that the Rect struct is almost as fast as doing the operations // manually since Rect is a value type. Rect topLeft = new Rect(_bounds.Left, _bounds.Top, w, h); Rect topRight = new Rect(_bounds.Left + w, _bounds.Top, w, h); Rect bottomLeft = new Rect(_bounds.Left, _bounds.Top + h, w, h); Rect bottomRight = new Rect(_bounds.Left + w, _bounds.Top + h, w, h); bool found = false; // See if any child quadrants completely contain this node. if(topLeft.IntersectsWith(bounds) && _topLeft != null) { found = _topLeft.HasIntersectingNodes(bounds); } if(!found && topRight.IntersectsWith(bounds) && _topRight != null) { found = _topRight.HasIntersectingNodes(bounds); } if(!found && bottomLeft.IntersectsWith(bounds) && _bottomLeft != null) { found = _bottomLeft.HasIntersectingNodes(bounds); } if(!found && bottomRight.IntersectsWith(bounds) && _bottomRight != null) { found = _bottomRight.HasIntersectingNodes(bounds); } if(!found) { found = HasIntersectingNodes(_nodes, bounds); } return found; } /// /// Walk the given linked list and test each node against the given bounds/ /// /// The last node in the circularly linked list. /// Bounds to test /// Return true if a node in the list intersects the bounds static bool HasIntersectingNodes(QuadNode last, Rect bounds) { if(last != null) { QuadNode n = last; do { n = n.Next; // first node. if(n.Bounds.IntersectsWith(bounds)) { return true; } } while(n != last); } return false; } /// /// Remove the given node from this Quadrant. /// /// The node to remove /// Returns true if the node was found and removed. internal bool RemoveNode(T node) { bool rc = false; if(_nodes != null) { QuadNode p = _nodes; while(p.Next.Node != node && p.Next != _nodes) { p = p.Next; } if(p.Next.Node == node) { rc = true; QuadNode n = p.Next; if(p == n) { // list goes to empty _nodes = null; } else { if(_nodes == n) _nodes = p; p.Next = n.Next; } } } return rc; } } /// /// This determines the overall quad-tree indexing strategy, changing this bounds /// is expensive since it has to re-divide the entire thing - like a re-hash operation. /// public Rect Bounds { get { return _bounds; } set { _bounds = value; ReIndex(); } } /// /// Insert a node with given bounds into this QuadTree. /// /// The node to insert /// The bounds of this node public void Insert(T node, Rect bounds) { if(_bounds.Width == 0 || _bounds.Height == 0) { // todo: localize. throw new InvalidOperationException("You must set a non-zero bounds on the QuadTree first"); } if(bounds.Width == 0 || bounds.Height == 0) { // todo: localize. throw new InvalidOperationException("Inserted node must have a non-zero width and height"); } if(_root == null) { _root = new Quadrant(null, _bounds); } Quadrant parent = _root.Insert(node, bounds); if(_table == null) { _table = new Dictionary(); } _table[node] = parent; } /// /// Get a list of the nodes that intersect the given bounds. /// /// The bounds to test /// List of zero or mode nodes found inside the given bounds public IEnumerable GetNodesInside(Rect bounds) { foreach(QuadNode n in GetNodes(bounds)) { yield return n.Node; } } /// /// Get a list of the nodes that intersect the given bounds. /// /// The bounds to test /// List of zero or mode nodes found inside the given bounds public bool HasNodesInside(Rect bounds) { if(_root != null) { _root.HasIntersectingNodes(bounds); } return false; } /// /// Get list of nodes that intersect the given bounds. /// /// The bounds to test /// The list of nodes intersecting the given bounds IEnumerable GetNodes(Rect bounds) { List result = new List(); if(_root != null) { _root.GetIntersectingNodes(result, bounds); } return result; } /// /// Remove the given node from this QuadTree. /// /// The node to remove /// True if the node was found and removed. public bool Remove(T node) { if(_table != null) { Quadrant parent = null; if(_table.TryGetValue(node, out parent)) { parent.RemoveNode(node); _table.Remove(node); return true; } } return false; } /// /// Rebuild all the Quadrants according to the current QuadTree Bounds. /// void ReIndex() { _root = null; foreach(QuadNode n in GetNodes(_bounds)) { // todo: it would be more efficient if we added a code path that allowed // reuse of the QuadNode wrappers. Insert(n.Node, n.Bounds); } } #if DEBUG_DUMP public void ShowQuadTree(Canvas container) { if (_root != null) { _root.ShowQuadTree(container); } } public void Dump(LogWriter w) { if (_root != null) { _root.Dump(w); } } #endif } #if DEBUG_DUMP public class LogWriter : IDisposable { XmlWriter _xw; int _indent; int _maxdepth; public LogWriter(TextWriter w) { XmlWriterSettings s = new XmlWriterSettings(); s.Indent = true; _xw = XmlWriter.Create(w, s); } public int MaxDepth { get { return _maxdepth; } } public void Open(string label) { _xw.WriteStartElement(label); _indent++; if (_indent > _maxdepth) _maxdepth = _indent; } public void Close() { _indent--; _xw.WriteEndElement(); } public void WriteAttribute(string name, string value) { _xw.WriteAttributeString(name, value); } #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing && _xw != null) { using (_xw) { _xw.Flush(); } _xw = null; } } #endregion } #endif }