diff --git a/vendor/kdtree.js b/vendor/kdtree/core.js similarity index 79% rename from vendor/kdtree.js rename to vendor/kdtree/core.js index 55f8cece4..db7549350 100644 --- a/vendor/kdtree.js +++ b/vendor/kdtree/core.js @@ -8,15 +8,15 @@ * @author Mircea Pricop , 2012 * @author Martin Kleppe , 2012 * @author Ubilabs http://ubilabs.net, 2012 - * @license MIT License + * @license MIT License */ -goog.provide("kdtree"); -goog.provide("kdtree.KDTree"); +goog.provide("kdtree.core"); +goog.provide("kdtree.core.KDTree"); -goog.require('goog.array'); -goog.require('goog.structs.Heap'); -goog.require('goog.asserts'); +goog.require("kdtree.heap"); +goog.require("goog.array"); +goog.require("goog.asserts"); goog.scope(function() { "use strict"; @@ -35,6 +35,10 @@ goog.scope(function() { } } + function precision(v) { + return parseFloat(v.toFixed(6)); + } + function buildTree(points, depth, parent, dimensions) { const dim = depth % dimensions; @@ -184,7 +188,6 @@ goog.scope(function() { node.left = null; node.obj = nextObj; } - } nearest(point, maxNodes) { @@ -192,29 +195,19 @@ goog.scope(function() { maxNodes = 1; } - let best = new goog.structs.Heap(); + let best = new kdtree.heap.MinHeap((x, y) => { + let res = x[1] - y[1]; + return res; + }); const nearestSearch = (node) => { - let bestChild; - const distance = this.metric(point, node.obj); - const dimension = node.dimension; - - if (best.getCount() < maxNodes || distance < best.peek()[1]) { - best.insert(-distance, [node.obj, distance]); - if (best.getCount() > maxNodes) { - best.remove(); - } - } + let distance = precision(this.metric(point, node.obj)); if (best.isEmpty()) { - best.insert(-distance, [node.obj, distance]); + best.insert([node.obj, distance]); } else { if (distance < best.peek()[1]) { - best.insert(-distance, [node.obj, distance]); - - if (best.getCount() > maxNodes) { - best.remove(); - } + best.insert([node.obj, distance]); } } @@ -222,12 +215,13 @@ goog.scope(function() { return; } + let bestChild = null; if (node.right === null) { bestChild = node.left; } else if (node.left === null) { bestChild = node.right; } else { - if (point[dimension] < node.obj[dimension]) { + if (point[node.dimension] < node.obj[node.dimension]) { bestChild = node.left; } else { bestChild = node.right; @@ -235,14 +229,41 @@ goog.scope(function() { } nearestSearch(bestChild); + + let candidate = [null, null]; + for (let i = 0; i < this.dimensions; i += 1) { + if (i === node.dimension) { + candidate[i] = point[i]; + } else { + candidate[i] = node.obj[i]; + } + } + + distance = Math.abs(this.metric(candidate, node.obj)); + + if (best.size < maxNodes || distance < best.peek()[1]) { + let otherChild; + if (bestChild === node.left) { + otherChild = node.right; + } else { + otherChild = node.left; + } + if (otherChild !== null) { + nearestSearch(otherChild); + } + } } if(this.root) { nearestSearch(this.root); } - result = best.getValues(); - result.sort((x) => x[1]); + const result = []; + + for (let i=0; i < (Math.min(maxNodes, best.size)); i++) { + result.push(best.removeHead()); + } + return result; } @@ -274,8 +295,8 @@ goog.scope(function() { }; // Types - kdtree.KDTree = KDTree; + kdtree.core.KDTree = KDTree; // Factory functions - kdtree.create2d = create2d; + kdtree.core.create2d = create2d; }); diff --git a/vendor/kdtree/heap.js b/vendor/kdtree/heap.js new file mode 100644 index 000000000..60d2fc987 --- /dev/null +++ b/vendor/kdtree/heap.js @@ -0,0 +1,112 @@ +/** + * kdtree + * + * Is a modified and google closure adapted kdtree implementation + * of https://github.com/ubilabs/kd-tree-javascript. + * + * @author Andrey Antukh , 2016 + * @license MIT License + */ + +goog.provide("kdtree.heap"); +goog.provide("kdtree.heap.MinHeap"); + +goog.scope(function() { + "use strict"; + + const compare = (x,y) => x-y; + + class MinHeap { + constructor(cmp) { + this.cmp = cmp || compare; + this.heap = []; + this.size = 0; + } + + insert(item) { + const heap = this.heap; + + let index = this.size++; + let parent = (index-1)>>1; + + heap[index] = item; + + while ((index > 0) && this.cmp(heap[parent], item) > 0) { + const tmp = heap[parent]; + heap[parent] = heap[index]; + heap[index] = tmp; + index = parent; + parent = (index-1)>>1; + } + } + + isEmpty() { + return this.size === 0; + } + + peek() { + return this.heap[0]; + } + + removeHead() { + const heap = this.heap; + const cmp = this.cmp; + + if (this.size === 0) { + return null; + } + + const head = heap[0]; + + this._bubble(0); + return head; + } + + remove(item) { + const heap = this.heap; + + for (let i = 0; i < this.size; ++i) { + if (heap[i] === item) { + this._bubble(i); + return true; + } + } + + return false; + } + + _bubble(index) { + const heap = this.heap; + const cmp = this.cmp; + + heap[index] = heap[--this.size]; + heap[this.size] = null; + + while (true) { + + const leftIndex = (index<<1)+1; + const rightIndex = (index<<1)+2; + let minIndex = index; + + if (leftIndex < this.size && cmp(heap[leftIndex], heap[minIndex]) < 0) { + minIndex = leftIndex; + } + + if (rightIndex < this.size && cmp(heap[rightIndex], heap[minIndex]) < 0) { + minIndex = rightIndex; + } + + if (minIndex !== index) { + const tmp = heap[index]; + heap[index] = heap[minIndex]; + heap[minIndex] = tmp; + index = minIndex; + } else { + break; + } + } + } + } + + kdtree.heap.MinHeap = MinHeap; +});