R Tree

第一步,创建R树类。

构建一个RTree生成器。用以创建tree对象。

例子:var tree = new RTree(12)

  1. var RTree = function(width){
  2. var _Min_Width = 3; // Minimum width of any node before a merge
  3. var _Max_Width = 6; // Maximum width of any node before a split
  4. if(!isNaN(width)){ _Min_Width = Math.floor(width/2.0); _Max_Width = width;}
  5. // Start with an empty root-tree
  6. var _T = {x:0, y:0, w:0, h:0, id:"root", nodes:[] };
  7.  
  8. var isArray = function(o) {
  9. return Object.prototype.toString.call(o) === '[object Array]';
  10. };
  11.  
  12. var _attach_data = function(node, more_tree){
  13. node.nodes = more_tree.nodes;
  14. node.x = more_tree.x; node.y = more_tree.y;
  15. node.w = more_tree.w; node.h = more_tree.h;
  16. return(node);
  17. };
  18.  
  19. //选择适合的节点来存放插入的条目。
  20. //@private。
  21. var _choose_leaf_subtree = (rect, root) => {...}
  22.  
  23. //内部插入函数。
  24. //[] = _insert_subtree(rectangle, object to insert, root to begin insertion at)
  25. //@private。即私有函数,只能用RTree的方法调用它。
  26. var _insert_subtree = (node, root) => {...}
  27.  
  28. this.get_tree = function() { return _T}
  29.  
  30. //new_tree代表新的子树节点,where代表要替代的位置。
  31. this.set_tree = (new_tree, where) => {
  32. if (!where) {
  33. where = _T
  34. }
  35. return (_attach_data(where, new_tree))
  36. }
  37.  
  38. //rect是边界矩阵,对象是叶子节点。
  39. this.insert = (rect, obj) => {
  40. if (arguments.length < 2) {
  41. throw "Wrong number of arguments"
  42. }
  43. return (_insert_subtree({x:rect.x, y:rect.y, w:rect.w, h:rect.h, leaf:obj}, _T))
  44. }
  45.  
  46. // End of RTree
  47. }

tree.insert方法,用以向生成的R树,插入数据。方法见下:

把一个新的索引条目E插入一个R树内:

  1. 找到插入新记录的位置: 这里要调用Choose Leaf方法,选择一个叶节点L来存放E。
  2. 把记录E加入到叶节点中: 这里需要进行判断。

    • 如果L.nodes <= M(即L的条目数量此时小于等于规定的最大值M),则下一步;
    • 否则, 需要分裂,调用Split Node方法。把叶子节点L分裂成2个新节点L和LL(2个新节点包含了原来的L节点的所有条目和新条目E)。
  3. 向上传递变化:调用Adjust Tree方法对L节点操作。如果上一步是分裂操作,则对2个新节点调用Adjust Tree方法。
  4. 判断:是否树增高。如果节点的分裂导致了root的分裂,则需要生成新的root,并且让它的两个孩子节点为原来的root分裂后产生的2个节点。

第二步:生成R树。

  1. var tree = new RTree(12)、
  2. tree.insert(rect, obj)

调用tree.insert(rect, obj)方法, 向R树插入数据,参数有2个,rect是边界矩阵对象,obj是节点对象。

由此,引入rectangle的构建器。

  1. //Rectangle - 生成rectangle对象。
  2. RTree.Rectangle = function(ix, iy, iw, ih) {
  3. var x, x2, y, y2, w, h;
  4.  
  5. if(ix.x) {
  6. x = ix.x;y = ix.y; //得到左下角坐标
  7.  
  8. if(ix.w !== 0 && !ix.w && ix.x2){
  9. //如果长,宽不存在,则计算出来。
  10. w = ix.x2-ix.x; h = ix.y2-ix.y;
  11. } else {
  12. w = ix.w; h = ix.h;
  13. }
  14.  
  15. x2 = x + w; y2 = y + h; //得到第右上角的坐标
  16. } else {
  17. x = ix; y = iy; //得到左下角坐标
  18. w = iw; h = ih;
  19. x2 = x + w; y2 = y + h; //得到第右上角的坐标
  20. }
  21.  
  22. this.x1 = this.x = x;
  23. this.y1 = this.y = y;
  24. this.x2 = x2;
  25. this.y2 = y2;
  26. this.w = w;
  27. this.h = h;
  28.  
  29. //矩阵a和当前矩阵产生部分重合则,返回true。
  30. this.overlap = (a) => {...}
  31. //扩展当前矩阵。根据传入的矩阵a,来扩展,包含矩阵a.
  32. this.expand = (a) => {...}
  33. //重置当前矩阵的坐标和长宽。代码同初始化Rectangle的代码。
  34. this.setRect = (ix, iy, iw ,ih) {...}
  35. // End of Rectangle
  36. }

overlap方法的解释(代码):

expand方法的解释:

插入一个矩阵,到叶子节点,对应的父亲的最小边界矩阵由此可能要扩展。矩阵b要把插入的a的矩阵包含进自己。

  1. this.expand = function(a) {
  2. var nx = Math.min(this.x(), a.x());
  3. var ny = Math.min(this.y(), a.y());
  4. w = Math.max(this.x2(), a.x2()) - nx;
  5. h = Math.max(this.y2(), a.y2()) - ny;
  6. x = nx; y = ny;
  7. return(this);
  8. };

第三步:插入方法讲解

这里要调用插入方法。

  1. //得到一个最小边界矩阵。
  2. var rect = new RTree.Rectangle(2,2,3,3)
  1. var tree = new RTree(12)
  2. tree.insert(rect, obj)

在插入一个rect后,为了要把rect放到正确的叶节点中。首先要找到这个叶节点,需要调用choose leaf方法。

  1. //在insert方法内的_insert方法内调用
  2. var tree_stack = _choose_leaf_subtree(node, root) //node参数就是最开始传入insert()的第一个参数

方法_choose_leaf_subtree:

  1. //选择适合的节点来存放插入的条目。
  2. //从root节点开始一路向下,每次找到当前节点的条目中,那个被插入新矩阵后,需要扩展最小的条目。就是被选择的条目。
  3. //直到到达叶子节点。最后返回:从root节点到叶子节点,经过的节点的集合数组。
  4. var _choose_leaf_subtree = (rect, root) => {
  5. var best_choice_index = -1; //记录最合适的节点索引,并用它来控制do..while循环
  6. var best_choice_stack = []; //方法结束后,返回从root节点到叶子节点,经过的节点的集合数组。
  7. var best_choice_area; //用于比较扩展的区域大小。
  8.  
  9. best_choice_stack.push(root); //返回的变量的第一个元素是root。
  10. var nodes = root.nodes; //首先,变量nodes记录根节点的所有的条目,用于循环代码。
  11.  
  12. do {
  13. if(best_choice_index != -1){
  14. best_choice_stack.push(nodes[best_choice_index]); //储存当前最合适的条目。
  15. nodes = nodes[best_choice_index].nodes; //修改nodes,为被选中的条目的nodes集合。其实就是准备下一层的条目的判断。
  16. best_choice_index = -1;
  17. }
  18.  
  19. //第一次循环,遍历root的所有项目。
  20. //变量:当前节点的所有条目/项目。找到添加rect后,扩展最小的那个条目。
  21. //当i等于-1,当前节点的所有条目的判断结束,
  22. for(var i = nodes.length-1; i >= 0; i--) {
  23. var ltree = nodes[i];
  24.  
  25. //如果到达叶节点,结束for循环。leaf是hash对象的索引,由_insert_subtree传入。
  26. if("leaf" in ltree) {
  27. best_choice_index = -1; //通过变量,同时也保证会退出do..while循环。
  28. break;
  29. }
  30. //下面的代码用于计算当前条目被插入新矩阵后,扩展的面积。然后用best_choice_area记录最小的扩张面积。
  31. //这里使用一种特殊的算法。
  32. //原矩阵正方化。一种算法。
  33. var old_lratio = RTree.Rectangle.squarified_ratio(ltree.w, ltree.h, ltree.nodes.length+1);
  34.  
  35. // 扩展矩阵。
  36. var nw = Math.max(ltree.x+ltree.w, rect.x+rect.w) - Math.min(ltree.x, rect.x);
  37. var nh = Math.max(ltree.y+ltree.h, rect.y+rect.h) - Math.min(ltree.y, rect.y);
  38.  
  39. // 新扩展的矩阵的正方化。
  40. var lratio = RTree.Rectangle.squarified_ratio(nw, nh, ltree.nodes.length+2);
  41.  
  42. //扩展的面积的比较,我们需要用变量记录最小扩展面积。
  43. if(best_choice_index < 0 || Math.abs(lratio - old_lratio) < best_choice_area) {
  44. best_choice_area = Math.abs(lratio - old_lratio);
  45. best_choice_index = i;
  46. }
  47. }
  48. } while(best_choice_index != -1)
  49.  
  50. return(best_choice_stack)
  51. }

找到要插入的位置后,进行第2步判断是否需要分裂节点。

再然后进行第3步,向上调整边界矩阵。

  • 这时要判断第二步是否有分裂节点的情况。如果是,那么对分裂出来的2个新节点的矩阵都要进行性调整。即调用RTree.Rectangle.expand_rectangle方法。
  • 如果false。就对原来的节点调整。

最后一路到达根节点,同样对根节点进行第2步的判断,第3步的调整,最后完成插入操作。

看_insert_subtree

  1. //[] = _insert_subtree(rectangle, object to insert, root to begin insertion at)
  2. //@private。即私有函数,只能用RTree的方法调用它。
  3. var _insert_subtree = (node, root) => {
  4. var bc //Best current node
  5.  
  6. // 初始化插入。如果根节点还没有儿子,那么这个节点的最小边界矩阵,就是root节点的MBR。
  7. if (root.nodes.length == 0) {
  8. root.x = node.x; root.y = node.y;
  9. root.w = node.w; root.h = node.h;
  10. root.nodes.push(node);
  11. return;
  12. }
  13.  
  14. //找到最适合的叶子节点来插入条目
  15. var tree_stack = _choose_leaf_subtree(node, root) //return得到从root到叶子,经过的所有节点的集合。
  16. var ret_obj = node //{x: rect.x, y:rect.y, w:rect.w, h:rect.h, leaf:obj}, 这个变量代表循环内要调整的条目。
  17.  
  18. //向上传递变化。包括插入的第2-4步骤,对tree_stack增减其中的元素,控制循环次数。
  19. do {
  20. //第一次循环会调用else块的语句,bc被赋值为叶节点对象, 同时tree_stack也发生变化,用于控制循环。
  21. if(bc && "nodes" in bc && bc.nodes.length == 0) { //handle the case of an empty node (from a split) 。 删除空节点。
  22. var pbc = bc; // Past bc
  23. bc = tree_stack.pop();
  24. for(var t=0; t<bc.nodes.length; t++)     //for循环没有带{},⚠️这种写法
  25. if(bc.nodes[t] === pbc || bc.nodes[t].nodes.length == 0) {
  26. bc.nodes.splice(t, 1); //删除这个条目。
  27. break;
  28.  }
  29. } else {
  30. bc = tree_stack.pop();
  31. }
  32.  
  33. // If there is data attached to this ret_obj,
  34. // 如果rec_obj对象含有属性"leaf",或"nodes",或一个数组(内含多2个新节点/条目)
  35. if("leaf" in ret_obj || "nodes" in ret_obj || isArray(ret_obj)) {
  36. // 调整和插入。
  37. if(isArray(ret_obj)) { //如果上一轮循环是分裂情况,那么需要把分裂的节点放入父亲点,并调整矩阵。
  38. for(var ai = 0; ai < ret_obj.length; ai++) { //让bc扩展到可以包含所有ret_obj内的条目。
  39. RTree.Rectangle.expand_rectangle(bc, ret_obj[ai]);
  40. }
  41. bc.nodes = bc.nodes.concat(ret_obj); //叶节点bc的条目增加
  42. } else { //正常情况,也是调整矩阵,然后插入。
  43. RTree.Rectangle.expand_rectangle(bc, ret_obj);
  44. bc.nodes.push(ret_obj); // 插入一个条目到节点bc。
  45. }
  46.  
  47.      //当插入完成后,第二步判断bc的条目是否超出最大值限制。
  48.       // true: rec_obj被重新赋值,因为没有"leaf",'nodes'属性,后续轮循环代表一路向上调整最小限定矩阵。
  49.       // false: 则需要分裂。然后也要对分裂后的节点进行调整。
  50. if(bc.nodes.length <= _Max_Width) {
  51. ret_obj = {x:bc.x,y:bc.y,w:bc.w,h:bc.h}; //后续循环只需调整MBR。
  52. } else { // 否则,要分裂
  53. // 调用linear_split(),返回包括2个新节点的数组。
  54. // formed from the split of the previous node's overflow
  55. var a = _linear_split(bc.nodes);
  56. ret_obj = a; //这时ret_obj是一个数组。
  57.  
  58. if(tree_stack.length < 1) { // 如果正在分裂root节点, tree_stack已经为空。这是插入操作的第4步。
  59. bc.nodes.push(a[0]);
  60. tree_stack.push(bc); //重新考虑root元素。
  61. ret_obj = a[1];
  62. } /*else {
  63. delete bc;
  64. }*/
  65. }
  66. } else {
  67.     //插入操作第3步骤。
  68.     //如果不是上面的情况:rect_obj只是一个含有矩阵信息的对象。就只更新bc的最小限定矩阵。
  69. RTree.Rectangle.expand_rectangle(bc, ret_obj);
  70. ret_obj = {x:bc.x,y:bc.y,w:bc.w,h:bc.h};
  71. }
  72. }while(tree_stack.length > 0)
  73. }

根据插入操作的流程,可理解代码。这里还没有讲解分裂方法:_linear_split()。

为了优化R树,大神们开发了多种分裂算法。这里使用的是linear split。

具体看 Hilbert R树发展 这篇文章讲解了分裂算法的发展历史。

⚠️R*树的方法是对R树最好的优化。

分裂算法也是很复杂的。没有仔细理解这个分裂算法。

  1. //split方法:分裂一个节点的条目,把它们放到2个新的节点中。⚠️分裂方法不同,放置也不同。这里使用linear split。
  2. // [ an array of two new arrays of nodes ] = linear_split(array of nodes)
  3. // @private
  4. var _linear_split = function(nodes) {
  5. var n = _pick_linear(nodes);
  6. while(nodes.length > 0) {
  7. _pick_next(nodes, n[0], n[1]);
  8. }
  9. return(n);
  10. };

里面的私有方法:

  1. pcik_linear返回数组,把原来数组内的条目,分成2组。每组的条目属于一个新的节点。
  2. pick_next则是把最好的MBR插入到节点a, b。

具体代码见:https://github.com/imbcmdth/RTree/blob/master/src/rtree.js

关于插入操作就讲解完了。

R树-javascript代码实现过程分析(插入操作)的更多相关文章

  1. 从B 树、B+ 树、B* 树谈到R 树

    从B 树.B+ 树.B* 树谈到R 树 作者:July.weedge.Frankie.编程艺术室出品. 说明:本文从B树开始谈起,然后论述B+树.B*树,最后谈到R 树.其中B树.B+树及B*树部分由 ...

  2. 从B树、B+树、B*树谈到R 树

    从B 树.B+ 树.B* 树谈到R 树 作者:July.weedge.Frankie.编程艺术室出品. 说明:本文从B树开始谈起,然后论述B+树.B*树,最后谈到R 树.其中B树.B+树及B*树部分由 ...

  3. B树,B+树,B*树以及R树的介绍

    https://blog.csdn.net/peterchan88/article/details/52248714 作者:July.weedge.Frankie.编程艺术室出品. 说明:本文从B树开 ...

  4. [转载]从B 树、B+ 树、B* 树谈到R 树

    从B 树.B+ 树.B* 树谈到R 树 作者:July.weedge.Frankie.编程艺术室出品. 说明:本文从B树开始谈起,然后论述B+树.B*树,最后谈到R 树.其中B树.B+树及B*树部分由 ...

  5. 从B 树、B+ 树、B* 树谈到R 树(转)

      作者:July.weedge.Frankie.编程艺术室出品. 说明:本文从B树开始谈起,然后论述B+树.B*树,最后谈到R 树.其中B树.B+树及B*树部分由weedge完成,R 树部分由Fra ...

  6. 彻底搞懂B树、B+树、B*树、R 树

    出处:http://blog.csdn.net/v_JULY_v . 第一节.B树.B+树.B*树1.前言: 动态查找树主要有:二叉查找树(Binary Search Tree),平衡二叉查找树(Ba ...

  7. R树空间索引

    R树在数据库等领域做出的功绩是非常显著的.它很好的解决了在高维空间搜索等问题.举个R树在现实领域中能够解决的例子吧:查找20英里以内所有的餐厅.如果没有R树你会怎么解决?一般情况下我们会把餐厅的坐标( ...

  8. 【转】R树空间索引

    R树在数据库等领域做出的功绩是非常显著的.它很好的解决了在高维空间搜索等问题.举个R树在现实领域中能够解决的例子吧:查找20英里以内所有的餐厅.如果没有R树你会怎么解决?一般情况下我们会把餐厅的坐标( ...

  9. R树的相关知识

    转自:http://blog.csdn.net/houzuoxin/article/details/16113895 R树在数据库等领域做出的功绩是非常显著的.它很好的解决了在高维空间搜索等问题.举个 ...

随机推荐

  1. 《精通并发与Netty》学习笔记(05 - Google Protobuf与Netty的结合)

    protobuf是由Google开发的一套对数据结构进行序列化的方法,可用做通信协议,数据存储格式,等等.其特点是不限语言.不限平台.扩展性强 Netty也提供了对Protobuf的天然支持,我们今天 ...

  2. 修改umask后apache报错:because search permissions are missing on a component of the path,

    0.修改umask后apache报错:because search permissions are missing on a component of the path, 1.ls -lrth ./h ...

  3. SQuirreL连接Phoenix报java.util.concurrent.TimeoutException

    1.表象 java.util.concurrent.TimeoutException at java.util.concurrent.FutureTask.get(FutureTask.java:20 ...

  4. P2709 小B的询问 【普通莫队】

    这是我的莫队入门题,我也了解到了莫队分为普通莫队以及带修莫队.顾名思义,普通莫队不需要修改区间的值,而带修莫队处理区间的值会修改的查询. 能用莫队的前提条件: 1.在知道 [l, r]中信息时,可以在 ...

  5. [bzoj2597][Wc2007]剪刀石头布_费用流

    [Wc2007]剪刀石头布 题目大意:https://www.lydsy.com/JudgeOnline/problem.php?id=2597 题解: 发现直接求三元环不好求,我们考虑任选三个点不是 ...

  6. Spring学习笔记(一)

    Spring学习笔记(一) 这是一个沉淀的过程,大概第一次接触Spring是在去年的这个时候,当初在实训,初次接触Java web,直接学习SSM框架(当是Servlet都没有学),于是,养成了一个很 ...

  7. 初学SpringBoot遇到的坑和笔记

    目录 1.日期返回给前端显示不理想问题 1.1重现 1.2原因 1.3解决 1.4结果 2.MyBatis-Plus数据库字段未找到问题 2.1重现 2.2原因 2.3解决 3.Long型雪花主键返回 ...

  8. T100——不绑定数据表字段,做查询条件(待改进)

    此例子使用的方法有待改善,不是很好的方法. 范例:aimm200: 作用:查询时默认不显示无效料件:新增参数控制查询是否显示无效料件.(只作用查询,不影响新增.修改等)

  9. INPUT和CONSTRUCT指令——范例报表查询,作用让用户输入数据,自动生成SQL的WHERE条件,带开窗查询

    INPUT指令 说明:1. 当程序执行到INPUT指令时,会将控制权交给用户,让用户输入数据.2. 用户输入完字段的数据,会将数据回传给程序中的变量接收.3. 只要执行到INPUT的指令,程序会将每个 ...

  10. WebMvcConfigurationSupport跨域和fastjson全局替换

    @Configuration public class WarnWebMvcConfigurationSupport extends WebMvcConfigurationSupport { /** ...