From 2fbd3f6007e81f7cb5f69dd49ac1c6a7b4dd6b03 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sat, 11 Jun 2016 15:09:37 +0300 Subject: [PATCH] Simplify kdtree impl removing unused code. --- vendor/kdtree/core.js | 300 ++++++++++++++---------------------------- 1 file changed, 101 insertions(+), 199 deletions(-) diff --git a/vendor/kdtree/core.js b/vendor/kdtree/core.js index 76a167e0f..cfce40563 100644 --- a/vendor/kdtree/core.js +++ b/vendor/kdtree/core.js @@ -11,6 +11,8 @@ * @license MIT License */ +"use strict"; + goog.provide("kdtree.core"); goog.provide("kdtree.core.KDTree"); @@ -18,10 +20,10 @@ goog.require("kdtree.heap"); goog.require("goog.asserts"); goog.scope(function() { - "use strict"; - const assert = goog.asserts.assert; - const assertNumber = goog.asserts.assertNumber; + + // Hardcoded dimensions value; + const dimensions = 2; class Node { constructor(obj, dimension, parent) { @@ -33,11 +35,43 @@ goog.scope(function() { } } + class KDTree { + constructor() { + this.root = null; + } + + initialize(points) { + assert(goog.isArray(points)); + this.root = buildTree(null, points, 0); + } + + isInitialized() { + return this.root !== null; + } + + clear() { + this.root = null + } + + nearest(point, maxNodes) { + assert(goog.isArray(point)); + assert(maxNodes >= 1); + assert(this.isInitialized()) + return searchNearest(this.root, point, maxNodes); + } + } + + // --- Private Api (implementation) + function precision(v) { return parseFloat(v.toFixed(6)); } - function buildTree(points, depth, parent, dimensions) { + function calculateDistance(a, b){ + return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2)); + } + + function buildTree(parent, points, depth) { const dim = depth % dimensions; if (points.length === 0) { @@ -54,229 +88,97 @@ goog.scope(function() { const median = Math.floor(points.length / 2); const node = new Node(points[median], dim, parent); - node.left = buildTree(points.slice(0, median), depth + 1, node, dimensions); - node.right = buildTree(points.slice(median + 1), depth + 1, node, dimensions); + node.left = buildTree(node, points.slice(0, median), depth + 1); + node.right = buildTree(node, points.slice(median + 1), depth + 1); return node; } - function findMin(node, dim) { - let dimension, own, left, right, min; - - if (node === null) { - return null; - } - - if (node.dimension === dim) { - if (node.left !== null) { - return findMin(node.left, dim); - } - return node; - } - - own = node.obj[dim]; - left = findMin(node.left, dim); - right = findMin(node.right, dim); - min = node; - - if (left !== null && left.obj[dim] < own) { - min = left; - } - if (right !== null && right.obj[dim] < min.obj[dim]) { - min = right; - } - return min; - } - - function innerSearch(point, node, parent) { - if (node === null) { - return parent; - } - - if (point[dim] < node.obj[dim]) { - return innerSearch(point, node.left, node); - } else { - return innerSearch(point, node.right, node); - } - } - - function nodeSearch(point, node) { - if (node === null) { - return null; - } - - if (node.obj === point) { - return node; - } - - if (point[node.dimension] < node.obj[node.dimension]) { - return nodeSearch(point, node.left); - } else { - return nodeSearch(point, node.right); - } - } - - class KDTree { - constructor(points, metric, dimensions) { - assert(points.length !== 0); - assertNumber(dimensions); - - this.root = buildTree(points, 0, null, dimensions); - this.metric = metric; - this.dimensions = dimensions; - } - - insert(point) { - const insertPosition = innerSearch(point, this.root, null); - - if (insertPosition === null) { - this.root = new Node(point, 0, null); - return; + function searchNearest(root, point, maxNodes) { + const search = (best, node) => { + if (best === null) { + best = new kdtree.heap.MinHeap((x, y) => x[1] - y[1]); } - const newNode = new Node(point, - (insertPosition.dimension + 1) % this.dimensions, - insertPosition); + let distance = precision(calculateDistance(point, node.obj)); - const dimension = insertPosition.dimension; - if (point[dimension] < insertPosition.obj[dimension]) { - insertPosition.left = newNode; + if (best.isEmpty()) { + best.insert([node.obj, distance]); } else { - insertPosition.right = newNode; - } - } - - remove(point) { - const node = nodeSearch(point, this.root); - if (node === null) { - return; - } - - if (node.left === null && node.right === null) { - if (node.parent === null) { - this.root = null; - return; - } - - const pdim = node.parent.dimension; - - if (node.obj[pdim] < node.parent.obj[pdim]) { - node.parent.left = null; - } else { - node.parent.right = null; - } - return; - } - - // If the right subtree is not empty, swap with the minimum element on the - // node's dimension. If it is empty, we swap the left and right subtrees and - // do the same. - let nextNode, nextObj; - - if (node.right !== null) { - nextNode = findMin(node.right, node.dimension); - nextObj = nextNode.obj; - removeNode(nextNode); - node.obj = nextObj; - } else { - nextNode = findMin(node.left, node.dimension); - nextObj = nextNode.obj; - removeNode(nextNode); - node.right = node.left; - node.left = null; - node.obj = nextObj; - } - } - - nearest(point, maxNodes) { - if (maxNodes === undefined) { - maxNodes = 1; - } - - let best = new kdtree.heap.MinHeap((x, y) => { - let res = x[1] - y[1]; - return res; - }); - - const nearestSearch = (node) => { - let distance = precision(this.metric(point, node.obj)); - - if (best.isEmpty()) { + if (distance < best.peek()[1]) { best.insert([node.obj, distance]); - } else { - if (distance < best.peek()[1]) { - best.insert([node.obj, distance]); - } } + } - if (node.right === null && node.left === null) { - return; - } + if (node.right === null && node.left === null) { + return best; + } - let bestChild = null; - if (node.right === null) { + let bestChild = null; + if (node.right === null) { + bestChild = node.left; + } else if (node.left === null) { + bestChild = node.right; + } else { + if (point[node.dimension] < node.obj[node.dimension]) { bestChild = node.left; - } else if (node.left === null) { - bestChild = node.right; } else { - if (point[node.dimension] < node.obj[node.dimension]) { - bestChild = node.left; - } else { - bestChild = node.right; - } + bestChild = node.right; } - - 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); } - const result = []; + best = search(best, bestChild); - for (let i=0; i < (Math.min(maxNodes, best.size)); i++) { - result.push(best.removeHead()); + let candidate = [null, null]; + for (let i = 0; i < dimensions; i += 1) { + if (i === node.dimension) { + candidate[i] = point[i]; + } else { + candidate[i] = node.obj[i]; + } } - return result; + distance = Math.abs(calculateDistance(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) { + best = search(best, otherChild); + } + } + + return best; + }; + + const best = search(null, root); + const result = []; + + for (let i=0; i < (Math.min(maxNodes, best.size)); i++) { + result.push(best.removeHead()); } + + return result; } - function distance2d(a, b){ - return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2)); - } + // --- Public Api - function create2d(points) { - return new KDTree(points, distance2d, 2); + function create(points) { + const tree = new KDTree(); + if (goog.isArray(points)) { + tree.initialize(points); + } + + return tree; }; // Types kdtree.core.KDTree = KDTree; // Factory functions - kdtree.core.create2d = create2d; + kdtree.core.create = create; });