关于四叉树的原理我想应该不需要多说啦,大家都懂得,实在不晓得的话,百度吧~
由于四叉树索引效率还可以并且非常简单,因此在Gis中广泛的应用也是毋庸置疑的。
本次就自己实现一个地图四叉树索引,但是还有一些问题也希望各位路过的大神能指点一下。
首先,结合一下应用场景(我们需要用四叉树来索引地图数据),考虑一下使用四叉树索引地图数据存在的一些问题。
1.什么时候建立四叉树索引,四叉树索引如何存储(序列化还是自己写算法来存储四叉树)?第一次加载数据的时候建立四叉树,当对地图数据操作的时候动态的维护四叉树,关闭地图的时候,保存内存中的四叉树实例,以避免每次建立四叉树耗时。目前我直接使用四叉树序列化。
2.什么东西可能会影响到四叉树索引地图数据的效率?树的深度,空节点每次还要被搜索,使用不平衡四叉树来避免无意义的搜索;每个节点上所关联的地图数据个数。子节点已经包含了地图数据的实例,则父节点不包含,减少数据的冗余。
为了可以在四叉树节点上关联地图数据,设计如下结构来表示地图数据。
///
/// 包含有包络线的对象接口定义
///
public interface IHasBoundingBox
{
///
/// 几何对象的包络线
///
BoundingBox Box
{
get;
set;
}
}
///
/// 几何对象的的ID和包络线构成索引对象
///
public class GeoBox:IHasBoundingBox
{
BoundingBox _box;
///
/// 几何对象的Id
///
public uint ID;
#region IHasBoundingBox 成员
public BoundingBox Box
{
get
{
return _box;
}
set
{
_box = value;
}
}
#endregion
}
用一个矩形范围和几何对象的Id来表示地图上需要索引的几何对象,当请求地图数据的时候,首先根据地图范围来遍历四叉树,找到对应四叉树的节点后,遍历该自树获取所有数据,即Id,根据Id去读取物理文件的数据。
四叉树节点的定义:
///
/// 四叉树节点
///
/// 节点中所包含的数据类型
public class QuadNode
{
private int _depth;
private List _datas;
private BoundingBox _rect;
private QuadNode _lt;
private QuadNode _rt;
private QuadNode _lb;
private QuadNode _rb;
private bool _isLeaf;
private QuadNodeLimit _limit;
public QuadNodeLimit Limit
{
get { return _limit; }
}
public bool IsLeaf
{
get { return _isLeaf; }
set { _isLeaf = value; }
}
public int Depth
{
get { return _depth; }
set { _depth = value; }
}
public List Datas
{
get { return _datas; }
set { _datas = value; }
}
public BoundingBox Rect
{
get { return _rect; }
set { _rect = value; }
}
public QuadNode LT
{
get { return _lt; }
set { _lt = value; }
}
public QuadNode RT
{
get { return _rt; }
set { _rt = value; }
}
public QuadNode LB
{
get { return _lb; }
set { _lb = value; }
}
public QuadNode RB
{
get { return _rb; }
set { _rb = value; }
}
public QuadNode(BoundingBox box,QuadNodeLimit limit, int depth)
{
_datas = new List();
_rect = box;
_lt = null;
_rt = null;
_lb = null;
_rb = null;
_isLeaf = true;
_depth = depth;
_limit = limit;
}
public bool Insert(T data)
{
QuadNode ret = QueryNode((data as IHasBoundingBox).Box);
if (ret != null)
{
//此处开始分裂子节点,不是平衡四叉树
if ( ret.Datas.Count >= _limit.MaxObjNum && ret.Depth < _limit.MaxDepth)
{
BoundingBox childBox;
double xmin = 0;
double ymin = 0;
double xmax = 0;
double ymax = 0;
// 开始分裂子节点
if (ret.LT == null)
{
xmin = _rect.Min.X;
ymin = _rect.Min.Y;
xmax = _rect.Min.X + _rect.Width / 2;
ymax = _rect.Min.Y + _rect.Height/2;
childBox = new BoundingBox(xmin, ymin, xmax, ymax);
if (childBox.Contains((data as IHasBoundingBox).Box))
{
ret.LT = new QuadNode(childBox, ret.Limit, ret.Depth + 1);
ret.IsLeaf = false;
SplitDatas(ret.LT, ret.Datas);
ret = ret.LT;
}
}
if (ret.RT == null)
{
xmin = _rect.Min.X + _rect.Width / 2;
ymin = _rect.Min.Y;
xmax = _rect.Max.X;
ymax = _rect.Min.Y + _rect.Height / 2;
childBox = new BoundingBox(xmin, ymin, xmax, ymax);
if (childBox.Contains((data as IHasBoundingBox).Box))
{
ret.RT = new QuadNode(childBox, ret.Limit, ret.Depth + 1);
ret.IsLeaf = false;
SplitDatas(ret.RT, ret.Datas);
ret = ret.RT;
}
}
if (ret.LB == null)
{
xmin = _rect.Min.X;
ymin = _rect.Min.Y + _rect.Height / 2;
xmax = _rect.Min.X + _rect.Width / 2;
ymax = _rect.Max.Y;
childBox = new BoundingBox(xmin, ymin, xmax, ymax);
if (childBox.Contains((data as IHasBoundingBox).Box))
{
ret.LB = new QuadNode(childBox, ret.Limit, ret.Depth + 1);
ret.IsLeaf = false;
SplitDatas(ret.LB, ret.Datas);
ret = ret.LB;
}
}
if (ret.RB == null)
{
xmin = _rect.Min.X + _rect.Width / 2;
ymin = _rect.Min.Y + _rect.Height / 2;
xmax = _rect.Max.X;
ymax = _rect.Max.Y;
childBox = new BoundingBox(xmin, ymin, xmax, ymax);
if (childBox.Contains((data as IHasBoundingBox).Box))
{
ret.RB = new QuadNode(childBox, ret.Limit, ret.Depth + 1);
ret.IsLeaf = false;
SplitDatas(ret.RB, ret.Datas);
ret = ret.RB;
}
}
}
ret.Datas.Add(data);
return true;
}
else
return false;
}
public void Remove(T data)
{
QuadNode ret = QueryNode((data as IHasBoundingBox).Box);
if (ret != null)
ret.Datas.Remove(data);
}
public QuadNode QueryNode(BoundingBox env)
{
QuadNode ret = null;
if (Rect.Contains(env) || Rect.Equals(env))
{
ret = this;
}
else if (env.Contains(Rect))
{
return this;
}
else
{
return null;
}
if (LT != null && LT.Rect.Contains(env))
{
ret = LT.QueryNode(env);
}
else if (RT != null && RT.Rect.Contains(env))
{
ret = RT.QueryNode(env);
}
else if (LB != null && LB.Rect.Contains(env))
{
ret = LB.QueryNode(env);
}
else if (RB != null && RB.Rect.Contains(env))
{
ret = RB.QueryNode(env);
}
return ret;
}
public void QueryData(QuadNode node, ref List datas)
{
QuadNode tempNode = node;
datas.AddRange(tempNode.Datas);
if (tempNode.LT != null)
{
QueryData(tempNode.LT, ref datas);
}
if (tempNode.LB != null)
{
QueryData(tempNode.LB, ref datas);
}
if (tempNode.RT != null)
{
QueryData(tempNode.RT, ref datas);
}
if (tempNode.RB != null)
{
QueryData(tempNode.RB, ref datas);
}
}
private void SplitDatas(QuadNode node, List datas)
{
for (int i = 0; i < datas.Count; i++)
{
if (node.Rect.Contains((datas[i] as IHasBoundingBox).Box))
{
node.Datas.Add(datas[i]);
}
}
//是否去掉父节点重复的节点,若是去掉获取数据消耗时间会变长,若是没有去掉则树中会有大量的冗余数据
for (int i = 0; i < node.Datas.Count; i++)
{
datas.Remove(node.Datas[i]);
}
}
}
四叉树的定义:
///
/// 四叉树地图索引
/// 四叉树必须要保证层数不能太深,同时也要保证节点保存对象数目不能大于规定的最大对象数目
/// 上述两个条件冲突时以深度为优先判断要素
/// 节点保存对象数目小于规定的最小保存数目时该节点则该节点不再分裂子节点
/// 注意:删除数据时该树不会自动萎缩,用户可以调用重新构造树的方法进行维护树
/// devCopper 2013.9.12
///
/// 保存对象的数据类型
public class QuadTree where T : IHasBoundingBox
{
QuadNode _root;
List _datas;
///
/// 四叉树的根节点
///
public QuadNode Root
{
get { return _root; }
set { _root = value; }
}
///
/// 所有被索引的对象
///
public List Datas
{
get { return _datas; }
set { _datas = value; }
}
public QuadTree()
{ }
///
/// 构造函数
///
/// 根节点的包围盒
public QuadTree(BoundingBox env, QuadNodeLimit limit)
{
_root = new QuadNode(env, limit, 0);
_datas = new List();
}
///
/// 插入一个对象
///
/// 插入的数据
public void Insert(T data)
{
_root.Insert(data);
_datas.Add(data);
}
///
/// 移除一个对象
///
/// 移除的对象,该对象的BoundingBox属性值不需要关心
public void Remove(T data)
{
_root.Remove(data);
_datas.Remove(data);
}
///
/// 遍历四叉树查询矩形框所包含的对象
///
/// 查询条件
/// 包含对象的节点
public List Query(BoundingBox env)
{
List data = new List();
QuadNode ret = _root.QueryNode(env);
if (ret != null)
ret.QueryData(ret, ref data);
return data;
}
///
/// 初始化四叉树
///
/// 初始化对象
public void Initialize(List datas)
{
for (int i = 0; i < datas.Count; i++)
{
if(_root.Insert(datas[i]))
_datas.Add(datas[i]);
}
}
///
/// 重新构造索引
///
public void Rebuild()
{
for (int i = 0; i < _datas.Count; i++)
{
_root.Insert(_datas[i]);
}
}
///
/// 从文件中读取四叉树索引
///
/// 文件
/// 一颗四叉树
public static QuadTree FromFile(string file)
{
System.IO.FileStream fs = new System.IO.FileStream(file, System.IO.FileMode.Open);
BinaryFormatter bf = new BinaryFormatter();
QuadTree tree = (QuadTree)bf.Deserialize(fs);
fs.Close();
return tree;
}
///
/// 保存四叉树索引
///
/// 四叉树对象
/// 保存的文件
public static void SaveIndex(QuadTree tree, string file)
{
System.IO.FileStream fs = new System.IO.FileStream(file, System.IO.FileMode.CreateNew);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fs, tree);
fs.Close();
}
}