quadtree.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /* quadTree node constructor */
  2. function TreeNode(data, x, y, dx, dy) {
  3. var dx_tmp = dx,
  4. dy_tmp = dy,
  5. msb_x = 0,
  6. msb_y = 0;
  7. /* left-bottom corner of current quadrant */
  8. this.x = x;
  9. this.y = y;
  10. /* minimum value in subtree under this node */
  11. this.lowerBound = null;
  12. /* maximum value in subtree under this node */
  13. this.upperBound = null;
  14. /*
  15. * child nodes are layed out in the following way:
  16. *
  17. * (x, y + 1) ---- (x + 1, y + 1)
  18. * | | |
  19. * | D | C |
  20. * | | |
  21. * |----------------------------|
  22. * | | |
  23. * | A | B |
  24. * | | |
  25. * (x, y) ------------ (x + 1, y)
  26. */
  27. this.childA = null;
  28. this.childB = null;
  29. this.childC = null;
  30. this.childD = null;
  31. if ((dx === 1) && (dy === 1)) {
  32. /* do not further subdivision */
  33. this.lowerBound = Math.min(
  34. data[y][x],
  35. data[y][x + 1],
  36. data[y + 1][x + 1],
  37. data[y + 1][x]
  38. );
  39. this.upperBound = Math.max(
  40. data[y][x],
  41. data[y][x + 1],
  42. data[y + 1][x + 1],
  43. data[y + 1][x]
  44. );
  45. } else {
  46. /* get most significant bit from dx */
  47. if (dx > 1) {
  48. while (dx_tmp !== 0) {
  49. dx_tmp = dx_tmp >> 1;
  50. msb_x++;
  51. }
  52. if (dx === (1 << (msb_x - 1)))
  53. msb_x--;
  54. dx_tmp = 1 << (msb_x - 1);
  55. }
  56. /* get most significant bit from dx */
  57. if (dy > 1) {
  58. while (dy_tmp !== 0) {
  59. dy_tmp = dy_tmp >> 1;
  60. msb_y++;
  61. }
  62. if (dy === (1 << (msb_y - 1)))
  63. msb_y--;
  64. dy_tmp = 1 << (msb_y - 1);
  65. }
  66. this.childA = new TreeNode(data, x, y, dx_tmp, dy_tmp);
  67. this.lowerBound = this.childA.lowerBound;
  68. this.upperBound = this.childA.upperBound;
  69. if (dx - dx_tmp > 0) {
  70. this.childB = new TreeNode(data, x + dx_tmp, y, dx - dx_tmp, dy_tmp);
  71. this.lowerBound = Math.min(this.lowerBound, this.childB.lowerBound);
  72. this.upperBound = Math.max(this.upperBound, this.childB.upperBound);
  73. if (dy - dy_tmp > 0) {
  74. this.childC = new TreeNode(data, x + dx_tmp, y + dy_tmp, dx - dx_tmp, dy - dy_tmp);
  75. this.lowerBound = Math.min(this.lowerBound, this.childC.lowerBound);
  76. this.upperBound = Math.max(this.upperBound, this.childC.upperBound);
  77. }
  78. }
  79. if (dy - dy_tmp > 0) {
  80. this.childD = new TreeNode(data, x, y + dy_tmp, dx_tmp, dy - dy_tmp);
  81. this.lowerBound = Math.min(this.lowerBound, this.childD.lowerBound);
  82. this.upperBound = Math.max(this.upperBound, this.childD.upperBound);
  83. }
  84. }
  85. }
  86. /**
  87. * Retrieve a list of cells within a particular range of values by
  88. * recursivly traversing the quad tree to it's leaves.
  89. *
  90. * @param subsumed If 'true' include all cells that are completely
  91. * subsumed within the specified range. Otherwise,
  92. * return only cells where at least one corner is
  93. * outside the specified range.
  94. *
  95. * @return An array of objects 'o' where each object has exactly two
  96. * properties: 'o.x' and 'o.y' denoting the left-bottom corner
  97. * of the corresponding cell.
  98. */
  99. TreeNode.prototype.cellsInBand = function(lowerBound, upperBound, subsumed) {
  100. var cells = [];
  101. subsumed = (typeof subsumed === 'undefined') ? true : subsumed;
  102. if ((this.lowerBound > upperBound) || (this.upperBound < lowerBound))
  103. return cells;
  104. if (!(this.childA || this.childB || this.childC || this.childD)) {
  105. if ((subsumed) ||
  106. (this.lowerBound <= lowerBound) ||
  107. (this.upperBound >= upperBound)) {
  108. cells.push({
  109. x: this.x,
  110. y: this.y
  111. });
  112. }
  113. } else {
  114. if (this.childA)
  115. cells = cells.concat(this.childA.cellsInBand(lowerBound, upperBound, subsumed));
  116. if (this.childB)
  117. cells = cells.concat(this.childB.cellsInBand(lowerBound, upperBound, subsumed));
  118. if (this.childD)
  119. cells = cells.concat(this.childD.cellsInBand(lowerBound, upperBound, subsumed));
  120. if (this.childC)
  121. cells = cells.concat(this.childC.cellsInBand(lowerBound, upperBound, subsumed));
  122. }
  123. return cells;
  124. };
  125. TreeNode.prototype.cellsBelowThreshold = function(threshold, subsumed) {
  126. var cells = [];
  127. subsumed = (typeof subsumed === 'undefined') ? true : subsumed;
  128. if (this.lowerBound > threshold)
  129. return cells;
  130. if (!(this.childA || this.childB || this.childC || this.childD)) {
  131. if ((subsumed) ||
  132. (this.upperBound >= threshold)) {
  133. cells.push({
  134. x: this.x,
  135. y: this.y
  136. });
  137. }
  138. } else {
  139. if (this.childA)
  140. cells = cells.concat(this.childA.cellsBelowThreshold(threshold, subsumed));
  141. if (this.childB)
  142. cells = cells.concat(this.childB.cellsBelowThreshold(threshold, subsumed));
  143. if (this.childD)
  144. cells = cells.concat(this.childD.cellsBelowThreshold(threshold, subsumed));
  145. if (this.childC)
  146. cells = cells.concat(this.childC.cellsBelowThreshold(threshold, subsumed));
  147. }
  148. return cells;
  149. };
  150. /*
  151. * Given a scalar field `data` construct a QuadTree
  152. * to efficiently lookup those parts of the scalar
  153. * field where values are within a particular
  154. * range of [lowerbound, upperbound] limits.
  155. */
  156. function QuadTree(data) {
  157. var i, cols;
  158. /* do some input checking */
  159. if (!data)
  160. throw new Error('data is required');
  161. if (!Array.isArray(data) ||
  162. !Array.isArray(data[0]))
  163. throw new Error('data must be scalar field, i.e. array of arrays');
  164. if (data.length < 2)
  165. throw new Error('data must contain at least two rows');
  166. /* check if we've got a regular grid */
  167. cols = data[0].length;
  168. if (cols < 2)
  169. throw new Error('data must contain at least two columns');
  170. for (i = 1; i < data.length; i++) {
  171. if (!Array.isArray(data[i]))
  172. throw new Error('Row ' + i + ' is not an array');
  173. if (data[i].length != cols)
  174. throw new Error('unequal row lengths detected, please provide a regular grid');
  175. }
  176. /* create pre-processing object */
  177. this.data = data;
  178. /* root node, i.e. entry to the data */
  179. this.root = new TreeNode(data, 0, 0, data[0].length - 1, data.length - 1);
  180. }
  181. export {
  182. QuadTree,
  183. QuadTree as quadTree
  184. };