KDTree.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import quickSelect from './quickSelect.js';
  41. var KDTreeNode =
  42. /** @class */
  43. function () {
  44. function KDTreeNode(axis, data) {
  45. this.axis = axis;
  46. this.data = data;
  47. }
  48. return KDTreeNode;
  49. }();
  50. /**
  51. * @constructor
  52. * @alias module:echarts/data/KDTree
  53. * @param {Array} points List of points.
  54. * each point needs an array property to represent the actual data
  55. * @param {Number} [dimension]
  56. * Point dimension.
  57. * Default will use the first point's length as dimension.
  58. */
  59. var KDTree =
  60. /** @class */
  61. function () {
  62. function KDTree(points, dimension) {
  63. // Use one stack to avoid allocation
  64. // each time searching the nearest point
  65. this._stack = []; // Again avoid allocating a new array
  66. // each time searching nearest N points
  67. this._nearstNList = [];
  68. if (!points.length) {
  69. return;
  70. }
  71. if (!dimension) {
  72. dimension = points[0].array.length;
  73. }
  74. this.dimension = dimension;
  75. this.root = this._buildTree(points, 0, points.length - 1, 0);
  76. }
  77. /**
  78. * Recursively build the tree.
  79. */
  80. KDTree.prototype._buildTree = function (points, left, right, axis) {
  81. if (right < left) {
  82. return null;
  83. }
  84. var medianIndex = Math.floor((left + right) / 2);
  85. medianIndex = quickSelect(points, left, right, medianIndex, function (a, b) {
  86. return a.array[axis] - b.array[axis];
  87. });
  88. var median = points[medianIndex];
  89. var node = new KDTreeNode(axis, median);
  90. axis = (axis + 1) % this.dimension;
  91. if (right > left) {
  92. node.left = this._buildTree(points, left, medianIndex - 1, axis);
  93. node.right = this._buildTree(points, medianIndex + 1, right, axis);
  94. }
  95. return node;
  96. };
  97. ;
  98. /**
  99. * Find nearest point
  100. * @param target Target point
  101. * @param squaredDistance Squared distance function
  102. * @return Nearest point
  103. */
  104. KDTree.prototype.nearest = function (target, squaredDistance) {
  105. var curr = this.root;
  106. var stack = this._stack;
  107. var idx = 0;
  108. var minDist = Infinity;
  109. var nearestNode = null;
  110. if (curr.data !== target) {
  111. minDist = squaredDistance(curr.data, target);
  112. nearestNode = curr;
  113. }
  114. if (target.array[curr.axis] < curr.data.array[curr.axis]) {
  115. // Left first
  116. curr.right && (stack[idx++] = curr.right);
  117. curr.left && (stack[idx++] = curr.left);
  118. } else {
  119. // Right first
  120. curr.left && (stack[idx++] = curr.left);
  121. curr.right && (stack[idx++] = curr.right);
  122. }
  123. while (idx--) {
  124. curr = stack[idx];
  125. var currDist = target.array[curr.axis] - curr.data.array[curr.axis];
  126. var isLeft = currDist < 0;
  127. var needsCheckOtherSide = false;
  128. currDist = currDist * currDist; // Intersecting right hyperplane with minDist hypersphere
  129. if (currDist < minDist) {
  130. currDist = squaredDistance(curr.data, target);
  131. if (currDist < minDist && curr.data !== target) {
  132. minDist = currDist;
  133. nearestNode = curr;
  134. }
  135. needsCheckOtherSide = true;
  136. }
  137. if (isLeft) {
  138. if (needsCheckOtherSide) {
  139. curr.right && (stack[idx++] = curr.right);
  140. } // Search in the left area
  141. curr.left && (stack[idx++] = curr.left);
  142. } else {
  143. if (needsCheckOtherSide) {
  144. curr.left && (stack[idx++] = curr.left);
  145. } // Search the right area
  146. curr.right && (stack[idx++] = curr.right);
  147. }
  148. }
  149. return nearestNode.data;
  150. };
  151. ;
  152. KDTree.prototype._addNearest = function (found, dist, node) {
  153. var nearestNList = this._nearstNList;
  154. var i = found - 1; // Insert to the right position
  155. // Sort from small to large
  156. for (; i > 0; i--) {
  157. if (dist >= nearestNList[i - 1].dist) {
  158. break;
  159. } else {
  160. nearestNList[i].dist = nearestNList[i - 1].dist;
  161. nearestNList[i].node = nearestNList[i - 1].node;
  162. }
  163. }
  164. nearestNList[i].dist = dist;
  165. nearestNList[i].node = node;
  166. };
  167. ;
  168. /**
  169. * Find nearest N points
  170. * @param target Target point
  171. * @param N
  172. * @param squaredDistance Squared distance function
  173. * @param output Output nearest N points
  174. */
  175. KDTree.prototype.nearestN = function (target, N, squaredDistance, output) {
  176. if (N <= 0) {
  177. output.length = 0;
  178. return output;
  179. }
  180. var curr = this.root;
  181. var stack = this._stack;
  182. var idx = 0;
  183. var nearestNList = this._nearstNList;
  184. for (var i = 0; i < N; i++) {
  185. // Allocate
  186. if (!nearestNList[i]) {
  187. nearestNList[i] = {
  188. dist: 0,
  189. node: null
  190. };
  191. }
  192. nearestNList[i].dist = 0;
  193. nearestNList[i].node = null;
  194. }
  195. var currDist = squaredDistance(curr.data, target);
  196. var found = 0;
  197. if (curr.data !== target) {
  198. found++;
  199. this._addNearest(found, currDist, curr);
  200. }
  201. if (target.array[curr.axis] < curr.data.array[curr.axis]) {
  202. // Left first
  203. curr.right && (stack[idx++] = curr.right);
  204. curr.left && (stack[idx++] = curr.left);
  205. } else {
  206. // Right first
  207. curr.left && (stack[idx++] = curr.left);
  208. curr.right && (stack[idx++] = curr.right);
  209. }
  210. while (idx--) {
  211. curr = stack[idx];
  212. var currDist_1 = target.array[curr.axis] - curr.data.array[curr.axis];
  213. var isLeft = currDist_1 < 0;
  214. var needsCheckOtherSide = false;
  215. currDist_1 = currDist_1 * currDist_1; // Intersecting right hyperplane with minDist hypersphere
  216. if (found < N || currDist_1 < nearestNList[found - 1].dist) {
  217. currDist_1 = squaredDistance(curr.data, target);
  218. if ((found < N || currDist_1 < nearestNList[found - 1].dist) && curr.data !== target) {
  219. if (found < N) {
  220. found++;
  221. }
  222. this._addNearest(found, currDist_1, curr);
  223. }
  224. needsCheckOtherSide = true;
  225. }
  226. if (isLeft) {
  227. if (needsCheckOtherSide) {
  228. curr.right && (stack[idx++] = curr.right);
  229. } // Search in the left area
  230. curr.left && (stack[idx++] = curr.left);
  231. } else {
  232. if (needsCheckOtherSide) {
  233. curr.left && (stack[idx++] = curr.left);
  234. } // Search the right area
  235. curr.right && (stack[idx++] = curr.right);
  236. }
  237. } // Copy to output
  238. for (var i = 0; i < found; i++) {
  239. output[i] = nearestNList[i].node.data;
  240. }
  241. output.length = found;
  242. return output;
  243. };
  244. return KDTree;
  245. }();
  246. export default KDTree;