From c98a8fb58fc9895da28bb9dde4a30bf22f92175d Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 10 May 2019 17:02:45 +0300 Subject: [PATCH 1/4] convert to new signature, more ES, update deps --- .travis.yml | 2 +- bench/bulk.bench.js | 10 ++-- bench/bulksearch.bench.js | 22 ++++---- bench/gendata.js | 6 +-- bench/insert.bench.js | 22 +++----- bench/perf.js | 4 +- bench/search.bench.js | 16 +++--- index.js | 111 +++++++++++++++++++------------------- package.json | 21 +++----- test/test.js | 76 +++++++++++++------------- 10 files changed, 135 insertions(+), 155 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0ce8cdf..d0369b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: node_js node_js: - - "8" + - "10" - "stable" diff --git a/bench/bulk.bench.js b/bench/bulk.bench.js index 52e31b4..4a1d8af 100644 --- a/bench/bulk.bench.js +++ b/bench/bulk.bench.js @@ -1,15 +1,15 @@ -var Benchmark = require('benchmark'), - rbush = require('../rbush'), - genData = require('./gendata'); +import Benchmark from 'benchmark'; +import RBush from '../index.js'; +import {generate} from './gendata'; var N = 10000, maxFill = 16; -var data = genData(N, 1); +var data = generate(N, 1); new Benchmark.Suite() .add('bulk loading ' + N + ' items (' + maxFill + ' node size)', function () { - var tree = rbush(maxFill); + var tree = new RBush(maxFill); tree.load(data); }) .on('error', function(event) { diff --git a/bench/bulksearch.bench.js b/bench/bulksearch.bench.js index 39b3af8..7a45d04 100644 --- a/bench/bulksearch.bench.js +++ b/bench/bulksearch.bench.js @@ -1,31 +1,31 @@ -var Benchmark = require('benchmark'), - rbush = require('../rbush'), - genData = require('./gendata'); +import Benchmark from 'benchmark'; +import RBush from '../index.js'; +import {generate} from './gendata'; var N = 10000, maxFill = 16; -var data = genData(N, 1); -var bboxes100 = genData(1000, 100 * Math.sqrt(0.1)); -var bboxes10 = genData(1000, 10); -var bboxes1 = genData(1000, 1); +var data = generate(N, 100); +var bboxes100 = generate(1000, 100 * Math.sqrt(0.1)); +var bboxes10 = generate(1000, 10); +var bboxes1 = generate(1000, 1); -var tree = rbush(maxFill); +var tree = new RBush(maxFill); tree.load(data); new Benchmark.Suite() .add('1000 searches 10% after bulk loading ' + N, function () { - for (i = 0; i < 1000; i++) { + for (var i = 0; i < 1000; i++) { tree.search(bboxes100[i]); } }) .add('1000 searches 1% after bulk loading ' + N, function () { - for (i = 0; i < 1000; i++) { + for (var i = 0; i < 1000; i++) { tree.search(bboxes10[i]); } }) .add('1000 searches 0.01% after bulk loading ' + N, function () { - for (i = 0; i < 1000; i++) { + for (var i = 0; i < 1000; i++) { tree.search(bboxes1[i]); } }) diff --git a/bench/gendata.js b/bench/gendata.js index 27bf1ae..dd8e8ff 100644 --- a/bench/gendata.js +++ b/bench/gendata.js @@ -1,6 +1,4 @@ -module.exports = genData; - function randBox(size) { var x = Math.random() * (100 - size), y = Math.random() * (100 - size); @@ -9,7 +7,7 @@ function randBox(size) { y + size * Math.random()]; } -function genData(N, size) { +export function generate(N, size) { var data = []; for (var i = 0; i < N; i++) { data.push(randBox(size)); @@ -17,7 +15,7 @@ function genData(N, size) { return data; }; -genData.convert = function (data) { +export function convert(data) { var result = []; for (var i = 0; i < data.length; i++) { result.push({x: data[i][0], y: data[i][1], w: data[i][2] - data[i][0], h: data[i][3] - data[i][1]}); diff --git a/bench/insert.bench.js b/bench/insert.bench.js index 6fc7ad5..c8afe85 100644 --- a/bench/insert.bench.js +++ b/bench/insert.bench.js @@ -1,29 +1,19 @@ -var Benchmark = require('benchmark'), - rbush = require('../rbush'), - genData = require('./gendata'); +import Benchmark from 'benchmark'; +import RBush from '../index.js'; +import {generate} from './gendata'; -var RTree = require('rtree'); - - -var N = 10000, +var N = 100, maxFill = 16; -var data = genData(N, 1); -var data2 = genData.convert(data); +var data = generate(N, 1); new Benchmark.Suite() .add('insert ' + N + ' items (' + maxFill + ' node size)', function () { - var tree = rbush(maxFill); + var tree = new RBush(maxFill); for (var i = 0; i < N; i++) { tree.insert(data[i]); } }) -.add('insert ' + N + ' items (' + maxFill + ' node size), old RTree', function () { - var tree2 = new RTree(maxFill); - for (var i = 0; i < N; i++) { - tree2.insert(data2[i], i); - } -}) .on('error', function(event) { console.log(event.target.error); }) diff --git a/bench/perf.js b/bench/perf.js index c7307de..756f735 100644 --- a/bench/perf.js +++ b/bench/perf.js @@ -1,4 +1,4 @@ -import rbush from '..'; +import RBush from '../index.js'; var N = 1000000, maxFill = 16; @@ -31,7 +31,7 @@ var bboxes100 = genData(1000, 100 * Math.sqrt(0.1)); var bboxes10 = genData(1000, 10); var bboxes1 = genData(1000, 1); -var tree = rbush(maxFill); +var tree = new RBush(maxFill); console.time('insert one by one'); for (var i = 0; i < N; i++) { diff --git a/bench/search.bench.js b/bench/search.bench.js index 0b03464..be6b4bf 100644 --- a/bench/search.bench.js +++ b/bench/search.bench.js @@ -1,16 +1,16 @@ -var Benchmark = require('benchmark'), - rbush = require('../rbush'), - genData = require('./gendata'); +import Benchmark from 'benchmark'; +import RBush from '../index.js'; +import {generate} from './gendata'; var N = 10000, maxFill = 16; -var data = genData(N, 1); -var bboxes100 = genData(1000, 100 * Math.sqrt(0.1)); -var bboxes10 = genData(1000, 10); -var bboxes1 = genData(1000, 1); +var data = generate(N, 1); +var bboxes100 = generate(1000, 100 * Math.sqrt(0.1)); +var bboxes10 = generate(1000, 10); +var bboxes1 = generate(1000, 1); -var tree = rbush(maxFill); +var tree = new RBush(maxFill); for (var i = 0; i < N; i++) { tree.insert(data[i]); } diff --git a/index.js b/index.js index 3da97c3..5612777 100644 --- a/index.js +++ b/index.js @@ -1,26 +1,23 @@ import quickselect from 'quickselect'; -export default function rbush(maxEntries, format) { - if (!(this instanceof rbush)) return new rbush(maxEntries, format); - - // max entries in a node is 9 by default; min node fill is 40% for best performance - this._maxEntries = Math.max(4, maxEntries || 9); - this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); +export default class RBush { + constructor(maxEntries, format) { + // max entries in a node is 9 by default; min node fill is 40% for best performance + this._maxEntries = Math.max(4, maxEntries || 9); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + + if (format) { + this._initFormat(format); + } - if (format) { - this._initFormat(format); + this.clear(); } - this.clear(); -} - -rbush.prototype = { - - all: function () { + all() { return this._all(this.data, []); - }, + } - search: function (bbox) { + search(bbox) { var node = this.data, result = [], @@ -47,9 +44,9 @@ rbush.prototype = { } return result; - }, + } - collides: function (bbox) { + collides(bbox) { var node = this.data, toBBox = this.toBBox; @@ -74,9 +71,9 @@ rbush.prototype = { } return false; - }, + } - load: function (data) { + load(data) { if (!(data && data.length)) return this; if (data.length < this._minEntries) { @@ -110,19 +107,19 @@ rbush.prototype = { } return this; - }, + } - insert: function (item) { + insert(item) { if (item) this._insert(item, this.data.height - 1); return this; - }, + } - clear: function () { + clear() { this.data = createNode([]); return this; - }, + } - remove: function (item, equalsFn) { + remove(item, equalsFn) { if (!item) return this; var node = this.data, @@ -169,21 +166,21 @@ rbush.prototype = { } return this; - }, + } - toBBox: function (item) { return item; }, + toBBox(item) { return item; } - compareMinX: compareNodeMinX, - compareMinY: compareNodeMinY, + compareMinX(a, b) { return a.minX - b.minX; } + compareMinY(a, b) { return a.minY - b.minY; } - toJSON: function () { return this.data; }, + toJSON() { return this.data; } - fromJSON: function (data) { + fromJSON(data) { this.data = data; return this; - }, + } - _all: function (node, result) { + _all(node, result) { var nodesToSearch = []; while (node) { if (node.leaf) result.push.apply(result, node.children); @@ -192,9 +189,9 @@ rbush.prototype = { node = nodesToSearch.pop(); } return result; - }, + } - _build: function (items, left, right, height) { + _build(items, left, right, height) { var N = right - left + 1, M = this._maxEntries, @@ -245,9 +242,9 @@ rbush.prototype = { calcBBox(node, this.toBBox); return node; - }, + } - _chooseSubtree: function (bbox, node, level, path) { + _chooseSubtree(bbox, node, level, path) { var i, len, child, targetNode, area, enlargement, minArea, minEnlargement; @@ -282,9 +279,9 @@ rbush.prototype = { } return node; - }, + } - _insert: function (item, level, isNode) { + _insert(item, level, isNode) { var toBBox = this.toBBox, bbox = isNode ? item : toBBox(item), @@ -307,10 +304,10 @@ rbush.prototype = { // adjust bboxes along the insertion path this._adjustParentBBoxes(bbox, insertPath, level); - }, + } // split overflowed node into two - _split: function (insertPath, level) { + _split(insertPath, level) { var node = insertPath[level], M = node.children.length, @@ -329,17 +326,17 @@ rbush.prototype = { if (level) insertPath[level - 1].children.push(newNode); else this._splitRoot(node, newNode); - }, + } - _splitRoot: function (node, newNode) { + _splitRoot(node, newNode) { // split root node this.data = createNode([node, newNode]); this.data.height = node.height + 1; this.data.leaf = false; calcBBox(this.data, this.toBBox); - }, + } - _chooseSplitIndex: function (node, m, M) { + _chooseSplitIndex(node, m, M) { var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index; @@ -369,10 +366,10 @@ rbush.prototype = { } return index; - }, + } // sorts node children by the best axis for split - _chooseSplitAxis: function (node, m, M) { + _chooseSplitAxis(node, m, M) { var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX, compareMinY = node.leaf ? this.compareMinY : compareNodeMinY, @@ -382,10 +379,10 @@ rbush.prototype = { // if total distributions margin value is minimal for x, sort by minX, // otherwise it's already sorted by minY if (xMargin < yMargin) node.children.sort(compareMinX); - }, + } // total margin of all possible split distributions where each node is at least m full - _allDistMargin: function (node, m, M, compare) { + _allDistMargin(node, m, M, compare) { node.children.sort(compare); @@ -408,16 +405,16 @@ rbush.prototype = { } return margin; - }, + } - _adjustParentBBoxes: function (bbox, path, level) { + _adjustParentBBoxes(bbox, path, level) { // adjust bboxes along the given tree path for (var i = level; i >= 0; i--) { extend(path[i], bbox); } - }, + } - _condense: function (path) { + _condense(path) { // go through the path, removing empty nodes and updating bboxes for (var i = path.length - 1, siblings; i >= 0; i--) { if (path[i].children.length === 0) { @@ -429,9 +426,9 @@ rbush.prototype = { } else calcBBox(path[i], this.toBBox); } - }, + } - _initFormat: function (format) { + _initFormat(format) { // data format (minX, minY, maxX, maxY accessors) // uses eval-type function compilation instead of just accepting a toBBox function @@ -449,7 +446,7 @@ rbush.prototype = { ', maxX: a' + format[2] + ', maxY: a' + format[3] + '};'); } -}; +} function findItem(item, items, equalsFn) { if (!equalsFn) return items.indexOf(item); diff --git a/package.json b/package.json index b945dc4..596f143 100644 --- a/package.json +++ b/package.json @@ -24,21 +24,20 @@ "unpkg": "rbush.min.js", "devDependencies": { "benchmark": "^2.1.4", + "c8": "^4.1.4", "eslint": "^4.13.1", "eslint-config-mourner": "^2.0.3", - "esm": "^3.0.84", - "faucet": "0.0.1", - "nyc": "^13.1.0", - "rollup": "^0.67.3", - "rollup-plugin-node-resolve": "^3.4.0", - "rollup-plugin-terser": "^3.0.0", - "tape": "^4.8.0" + "esm": "^3.2.22", + "rollup": "^1.11.3", + "rollup-plugin-node-resolve": "^4.2.3", + "rollup-plugin-terser": "^4.0.4", + "tape": "^4.10.1" }, "scripts": { "pretest": "eslint index.js test/test.js ", - "test": "node -r esm test/test.js | faucet", + "test": "tape -r esm test/test.js", "perf": "node -r esm ./bench/perf.js", - "cov": "nyc --check-coverage --require esm npm test", + "cov": "c8 npm run test", "build": "rollup -c", "prepare": "npm run build", "prepublishOnly": "npm run build" @@ -52,10 +51,6 @@ "extends": "mourner", "parserOptions": { "sourceType": "module" - }, - "rules": { - "new-cap": 0, - "consistent-return": 0 } }, "dependencies": { diff --git a/test/test.js b/test/test.js index 1cb0969..6f5d4b2 100644 --- a/test/test.js +++ b/test/test.js @@ -1,7 +1,7 @@ /*eslint key-spacing: 0, comma-spacing: 0 */ -import rbush from '..'; +import RBush from '../index.js'; import t from 'tape'; function sortedEqual(t, a, b, compare) { @@ -44,24 +44,24 @@ var emptyData = [[-Infinity, -Infinity, Infinity, Infinity],[-Infinity, -Infinit [-Infinity, -Infinity, Infinity, Infinity],[-Infinity, -Infinity, Infinity, Infinity]].map(arrToBBox); t('constructor accepts a format argument to customize the data format', function (t) { - var tree = rbush(4, ['.minLng', '.minLat', '.maxLng', '.maxLat']); + var tree = new RBush(4, ['.minLng', '.minLat', '.maxLng', '.maxLat']); t.same(tree.toBBox({minLng: 1, minLat: 2, maxLng: 3, maxLat: 4}), {minX: 1, minY: 2, maxX: 3, maxY: 4}); t.end(); }); t('constructor uses 9 max entries by default', function (t) { - var tree = rbush().load(someData(9)); + var tree = new RBush().load(someData(9)); t.equal(tree.toJSON().height, 1); - var tree2 = rbush().load(someData(10)); + var tree2 = new RBush().load(someData(10)); t.equal(tree2.toJSON().height, 2); t.end(); }); t('#toBBox, #compareMinX, #compareMinY can be overriden to allow custom data structures', function (t) { - var tree = rbush(4); + var tree = new RBush(4); tree.toBBox = function (item) { return { minX: item.minLng, @@ -122,7 +122,7 @@ t('#toBBox, #compareMinX, #compareMinY can be overriden to allow custom data str t('#load bulk-loads the given data given max node entries and forms a proper search tree', function (t) { - var tree = rbush(4).load(data); + var tree = new RBush(4).load(data); sortedEqual(t, tree.all(), data); t.end(); @@ -130,11 +130,11 @@ t('#load bulk-loads the given data given max node entries and forms a proper sea t('#load uses standard insertion when given a low number of items', function (t) { - var tree = rbush(8) + var tree = new RBush(8) .load(data) .load(data.slice(0, 3)); - var tree2 = rbush(8) + var tree2 = new RBush(8) .load(data) .insert(data[0]) .insert(data[1]) @@ -145,14 +145,14 @@ t('#load uses standard insertion when given a low number of items', function (t) }); t('#load does nothing if loading empty data', function (t) { - var tree = rbush().load([]); + var tree = new RBush().load([]); - t.same(tree.toJSON(), rbush().toJSON()); + t.same(tree.toJSON(), new RBush().toJSON()); t.end(); }); t('#load handles the insertion of maxEntries + 2 empty bboxes', function (t) { - var tree = rbush(4) + var tree = new RBush(4) .load(emptyData); t.equal(tree.toJSON().height, 2); @@ -162,7 +162,7 @@ t('#load handles the insertion of maxEntries + 2 empty bboxes', function (t) { }); t('#insert handles the insertion of maxEntries + 2 empty bboxes', function (t) { - var tree = rbush(4); + var tree = new RBush(4); emptyData.forEach(function (datum) { tree.insert(datum); @@ -175,7 +175,7 @@ t('#insert handles the insertion of maxEntries + 2 empty bboxes', function (t) { }); t('#load properly splits tree root when merging trees of the same height', function (t) { - var tree = rbush(4) + var tree = new RBush(4) .load(data) .load(data); @@ -188,11 +188,11 @@ t('#load properly splits tree root when merging trees of the same height', funct t('#load properly merges data of smaller or bigger tree heights', function (t) { var smaller = someData(10); - var tree1 = rbush(4) + var tree1 = new RBush(4) .load(data) .load(smaller); - var tree2 = rbush(4) + var tree2 = new RBush(4) .load(smaller) .load(data); @@ -206,7 +206,7 @@ t('#load properly merges data of smaller or bigger tree heights', function (t) { t('#search finds matching points in the tree given a bbox', function (t) { - var tree = rbush(4).load(data); + var tree = new RBush(4).load(data); var result = tree.search({minX: 40, minY: 20, maxX: 80, maxY: 70}); sortedEqual(t, result, [ @@ -219,7 +219,7 @@ t('#search finds matching points in the tree given a bbox', function (t) { t('#collides returns true when search finds matching points', function (t) { - var tree = rbush(4).load(data); + var tree = new RBush(4).load(data); var result = tree.collides({minX: 40, minY: 20, maxX: 80, maxY: 70}); t.same(result, true); @@ -228,14 +228,14 @@ t('#collides returns true when search finds matching points', function (t) { }); t('#search returns an empty array if nothing found', function (t) { - var result = rbush(4).load(data).search([200, 200, 210, 210]); + var result = new RBush(4).load(data).search([200, 200, 210, 210]); t.same(result, []); t.end(); }); t('#collides returns false if nothing found', function (t) { - var result = rbush(4).load(data).collides([200, 200, 210, 210]); + var result = new RBush(4).load(data).collides([200, 200, 210, 210]); t.same(result, false); t.end(); @@ -243,7 +243,7 @@ t('#collides returns false if nothing found', function (t) { t('#all returns all points in the tree', function (t) { - var tree = rbush(4).load(data); + var tree = new RBush(4).load(data); var result = tree.all(); sortedEqual(t, result, data); @@ -254,8 +254,8 @@ t('#all returns all points in the tree', function (t) { t('#toJSON & #fromJSON exports and imports search tree in JSON format', function (t) { - var tree = rbush(4).load(data); - var tree2 = rbush(4).fromJSON(tree.data); + var tree = new RBush(4).load(data); + var tree2 = new RBush(4).fromJSON(tree.data); sortedEqual(t, tree.all(), tree2.all()); t.end(); @@ -270,7 +270,7 @@ t('#insert adds an item to an existing tree correctly', function (t) { [1, 1, 2, 2] ].map(arrToBBox); - var tree = rbush(4).load(items.slice(0, 3)); + var tree = new RBush(4).load(items.slice(0, 3)); tree.insert(items[3]); t.equal(tree.toJSON().height, 1); @@ -285,19 +285,19 @@ t('#insert adds an item to an existing tree correctly', function (t) { t('#insert does nothing if given undefined', function (t) { t.same( - rbush().load(data), - rbush().load(data).insert()); + new RBush().load(data), + new RBush().load(data).insert()); t.end(); }); t('#insert forms a valid tree if items are inserted one by one', function (t) { - var tree = rbush(4); + var tree = new RBush(4); for (var i = 0; i < data.length; i++) { tree.insert(data[i]); } - var tree2 = rbush(4).load(data); + var tree2 = new RBush(4).load(data); t.ok(tree.toJSON().height - tree2.toJSON().height <= 1); @@ -306,7 +306,7 @@ t('#insert forms a valid tree if items are inserted one by one', function (t) { }); t('#remove removes items correctly', function (t) { - var tree = rbush(4).load(data); + var tree = new RBush(4).load(data); var len = data.length; @@ -325,28 +325,28 @@ t('#remove removes items correctly', function (t) { }); t('#remove does nothing if nothing found', function (t) { t.same( - rbush().load(data), - rbush().load(data).remove([13, 13, 13, 13])); + new RBush().load(data), + new RBush().load(data).remove([13, 13, 13, 13])); t.end(); }); t('#remove does nothing if given undefined', function (t) { t.same( - rbush().load(data), - rbush().load(data).remove()); + new RBush().load(data), + new RBush().load(data).remove()); t.end(); }); t('#remove brings the tree to a clear state when removing everything one by one', function (t) { - var tree = rbush(4).load(data); + var tree = new RBush(4).load(data); for (var i = 0; i < data.length; i++) { tree.remove(data[i]); } - t.same(tree.toJSON(), rbush(4).toJSON()); + t.same(tree.toJSON(), new RBush(4).toJSON()); t.end(); }); t('#remove accepts an equals function', function (t) { - var tree = rbush(4).load(data); + var tree = new RBush(4).load(data); var item = {minX: 20, minY: 70, maxX: 20, maxY: 70, foo: 'bar'}; @@ -361,14 +361,14 @@ t('#remove accepts an equals function', function (t) { t('#clear should clear all the data in the tree', function (t) { t.same( - rbush(4).load(data).clear().toJSON(), - rbush(4).toJSON()); + new RBush(4).load(data).clear().toJSON(), + new RBush(4).toJSON()); t.end(); }); t('should have chainable API', function (t) { t.doesNotThrow(function () { - rbush() + new RBush() .load(data) .insert(data[0]) .remove(data[0]); From f908fec5125eb8e7d78371e8b274417665bba74c Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 10 May 2019 17:34:08 +0300 Subject: [PATCH 2/4] convert more code to ES --- index.js | 191 +++++++++++++++++++++++---------------------------- package.json | 11 ++- test/test.js | 176 ++++++++++++++++++++++------------------------- 3 files changed, 172 insertions(+), 206 deletions(-) diff --git a/index.js b/index.js index 5612777..9ac1836 100644 --- a/index.js +++ b/index.js @@ -18,21 +18,18 @@ export default class RBush { } search(bbox) { - - var node = this.data, - result = [], - toBBox = this.toBBox; + let node = this.data; + const result = []; if (!intersects(bbox, node)) return result; - var nodesToSearch = [], - i, len, child, childBBox; + const toBBox = this.toBBox; + const nodesToSearch = []; while (node) { - for (i = 0, len = node.children.length; i < len; i++) { - - child = node.children[i]; - childBBox = node.leaf ? toBBox(child) : child; + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const childBBox = node.leaf ? toBBox(child) : child; if (intersects(bbox, childBBox)) { if (node.leaf) result.push(child); @@ -47,20 +44,15 @@ export default class RBush { } collides(bbox) { - - var node = this.data, - toBBox = this.toBBox; + let node = this.data; if (!intersects(bbox, node)) return false; - var nodesToSearch = [], - i, len, child, childBBox; - + const nodesToSearch = []; while (node) { - for (i = 0, len = node.children.length; i < len; i++) { - - child = node.children[i]; - childBBox = node.leaf ? toBBox(child) : child; + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const childBBox = node.leaf ? this.toBBox(child) : child; if (intersects(bbox, childBBox)) { if (node.leaf || contains(bbox, childBBox)) return true; @@ -77,14 +69,14 @@ export default class RBush { if (!(data && data.length)) return this; if (data.length < this._minEntries) { - for (var i = 0, len = data.length; i < len; i++) { + for (let i = 0; i < data.length; i++) { this.insert(data[i]); } return this; } // recursively build the tree with the given data from scratch using OMT algorithm - var node = this._build(data.slice(), 0, data.length - 1, 0); + let node = this._build(data.slice(), 0, data.length - 1, 0); if (!this.data.children.length) { // save as is if tree is empty @@ -97,7 +89,7 @@ export default class RBush { } else { if (this.data.height < node.height) { // swap trees if inserted one is bigger - var tmpNode = this.data; + const tmpNode = this.data; this.data = node; node = tmpNode; } @@ -122,11 +114,11 @@ export default class RBush { remove(item, equalsFn) { if (!item) return this; - var node = this.data, - bbox = this.toBBox(item), - path = [], - indexes = [], - i, parent, index, goingUp; + let node = this.data; + const bbox = this.toBBox(item); + const path = []; + const indexes = []; + let i, parent, goingUp; // depth-first iterative tree traversal while (node || path.length) { @@ -139,7 +131,7 @@ export default class RBush { } if (node.leaf) { // check current node - index = findItem(item, node.children, equalsFn); + const index = findItem(item, node.children, equalsFn); if (index !== -1) { // item found, remove the item and condense tree upwards @@ -181,10 +173,10 @@ export default class RBush { } _all(node, result) { - var nodesToSearch = []; + const nodesToSearch = []; while (node) { - if (node.leaf) result.push.apply(result, node.children); - else nodesToSearch.push.apply(nodesToSearch, node.children); + if (node.leaf) result.push(...node.children); + else nodesToSearch.push(...node.children); node = nodesToSearch.pop(); } @@ -193,9 +185,9 @@ export default class RBush { _build(items, left, right, height) { - var N = right - left + 1, - M = this._maxEntries, - node; + const N = right - left + 1; + let M = this._maxEntries; + let node; if (N <= M) { // reached leaf level; return leaf @@ -218,21 +210,20 @@ export default class RBush { // split the items into M mostly square tiles - var N2 = Math.ceil(N / M), - N1 = N2 * Math.ceil(Math.sqrt(M)), - i, j, right2, right3; + const N2 = Math.ceil(N / M); + const N1 = N2 * Math.ceil(Math.sqrt(M)); multiSelect(items, left, right, N1, this.compareMinX); - for (i = left; i <= right; i += N1) { + for (let i = left; i <= right; i += N1) { - right2 = Math.min(i + N1 - 1, right); + const right2 = Math.min(i + N1 - 1, right); multiSelect(items, i, right2, N2, this.compareMinY); - for (j = i; j <= right2; j += N2) { + for (let j = i; j <= right2; j += N2) { - right3 = Math.min(j + N2 - 1, right2); + const right3 = Math.min(j + N2 - 1, right2); // pack each entry recursively node.children.push(this._build(items, j, right3, height - 1)); @@ -245,20 +236,19 @@ export default class RBush { } _chooseSubtree(bbox, node, level, path) { - - var i, len, child, targetNode, area, enlargement, minArea, minEnlargement; - while (true) { path.push(node); if (node.leaf || path.length - 1 === level) break; - minArea = minEnlargement = Infinity; + let minArea = Infinity; + let minEnlargement = Infinity; + let targetNode; - for (i = 0, len = node.children.length; i < len; i++) { - child = node.children[i]; - area = bboxArea(child); - enlargement = enlargedArea(bbox, child) - area; + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const area = bboxArea(child); + const enlargement = enlargedArea(bbox, child) - area; // choose entry with the least area enlargement if (enlargement < minEnlargement) { @@ -282,13 +272,11 @@ export default class RBush { } _insert(item, level, isNode) { - - var toBBox = this.toBBox, - bbox = isNode ? item : toBBox(item), - insertPath = []; + const bbox = isNode ? item : this.toBBox(item); + const insertPath = []; // find the best node for accommodating the item, saving all nodes along the path too - var node = this._chooseSubtree(bbox, this.data, level, insertPath); + const node = this._chooseSubtree(bbox, this.data, level, insertPath); // put the item into the node node.children.push(item); @@ -308,16 +296,15 @@ export default class RBush { // split overflowed node into two _split(insertPath, level) { - - var node = insertPath[level], - M = node.children.length, - m = this._minEntries; + const node = insertPath[level]; + const M = node.children.length; + const m = this._minEntries; this._chooseSplitAxis(node, m, M); - var splitIndex = this._chooseSplitIndex(node, m, M); + const splitIndex = this._chooseSplitIndex(node, m, M); - var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); + const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); newNode.height = node.height; newNode.leaf = node.leaf; @@ -337,17 +324,16 @@ export default class RBush { } _chooseSplitIndex(node, m, M) { + let index; + let minOverlap = Infinity; + let minArea = Infinity; - var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index; - - minOverlap = minArea = Infinity; + for (let i = m; i <= M - m; i++) { + const bbox1 = distBBox(node, 0, i, this.toBBox); + const bbox2 = distBBox(node, i, M, this.toBBox); - for (i = m; i <= M - m; i++) { - bbox1 = distBBox(node, 0, i, this.toBBox); - bbox2 = distBBox(node, i, M, this.toBBox); - - overlap = intersectionArea(bbox1, bbox2); - area = bboxArea(bbox1) + bboxArea(bbox2); + const overlap = intersectionArea(bbox1, bbox2); + const area = bboxArea(bbox1) + bboxArea(bbox2); // choose distribution with minimum overlap if (overlap < minOverlap) { @@ -370,11 +356,10 @@ export default class RBush { // sorts node children by the best axis for split _chooseSplitAxis(node, m, M) { - - var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX, - compareMinY = node.leaf ? this.compareMinY : compareNodeMinY, - xMargin = this._allDistMargin(node, m, M, compareMinX), - yMargin = this._allDistMargin(node, m, M, compareMinY); + const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX; + const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY; + const xMargin = this._allDistMargin(node, m, M, compareMinX); + const yMargin = this._allDistMargin(node, m, M, compareMinY); // if total distributions margin value is minimal for x, sort by minX, // otherwise it's already sorted by minY @@ -383,23 +368,21 @@ export default class RBush { // total margin of all possible split distributions where each node is at least m full _allDistMargin(node, m, M, compare) { - node.children.sort(compare); - var toBBox = this.toBBox, - leftBBox = distBBox(node, 0, m, toBBox), - rightBBox = distBBox(node, M - m, M, toBBox), - margin = bboxMargin(leftBBox) + bboxMargin(rightBBox), - i, child; + const toBBox = this.toBBox; + const leftBBox = distBBox(node, 0, m, toBBox); + const rightBBox = distBBox(node, M - m, M, toBBox); + let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox); - for (i = m; i < M - m; i++) { - child = node.children[i]; + for (let i = m; i < M - m; i++) { + const child = node.children[i]; extend(leftBBox, node.leaf ? toBBox(child) : child); margin += bboxMargin(leftBBox); } - for (i = M - m - 1; i >= m; i--) { - child = node.children[i]; + for (let i = M - m - 1; i >= m; i--) { + const child = node.children[i]; extend(rightBBox, node.leaf ? toBBox(child) : child); margin += bboxMargin(rightBBox); } @@ -409,14 +392,14 @@ export default class RBush { _adjustParentBBoxes(bbox, path, level) { // adjust bboxes along the given tree path - for (var i = level; i >= 0; i--) { + for (let i = level; i >= 0; i--) { extend(path[i], bbox); } } _condense(path) { // go through the path, removing empty nodes and updating bboxes - for (var i = path.length - 1, siblings; i >= 0; i--) { + for (let i = path.length - 1, siblings; i >= 0; i--) { if (path[i].children.length === 0) { if (i > 0) { siblings = path[i - 1].children; @@ -435,23 +418,24 @@ export default class RBush { // because the algorithms are very sensitive to sorting functions performance, // so they should be dead simple and without inner calls - var compareArr = ['return a', ' - b', ';']; + const compareArr = ['return a', ' - b', ';']; this.compareMinX = new Function('a', 'b', compareArr.join(format[0])); this.compareMinY = new Function('a', 'b', compareArr.join(format[1])); - this.toBBox = new Function('a', - 'return {minX: a' + format[0] + - ', minY: a' + format[1] + - ', maxX: a' + format[2] + - ', maxY: a' + format[3] + '};'); + this.toBBox = new Function('a', `return { + minX: a${format[0]}, + minY: a${format[1]}, + maxX: a${format[2]}, + maxY: a${format[3]} + };`); } } function findItem(item, items, equalsFn) { if (!equalsFn) return items.indexOf(item); - for (var i = 0; i < items.length; i++) { + for (let i = 0; i < items.length; i++) { if (equalsFn(item, items[i])) return i; } return -1; @@ -470,8 +454,8 @@ function distBBox(node, k, p, toBBox, destNode) { destNode.maxX = -Infinity; destNode.maxY = -Infinity; - for (var i = k, child; i < p; i++) { - child = node.children[i]; + for (let i = k; i < p; i++) { + const child = node.children[i]; extend(destNode, node.leaf ? toBBox(child) : child); } @@ -498,10 +482,10 @@ function enlargedArea(a, b) { } function intersectionArea(a, b) { - var minX = Math.max(a.minX, b.minX), - minY = Math.max(a.minY, b.minY), - maxX = Math.min(a.maxX, b.maxX), - maxY = Math.min(a.maxY, b.maxY); + const minX = Math.max(a.minX, b.minX); + const minY = Math.max(a.minY, b.minY); + const maxX = Math.min(a.maxX, b.maxX); + const maxY = Math.min(a.maxY, b.maxY); return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); @@ -523,7 +507,7 @@ function intersects(a, b) { function createNode(children) { return { - children: children, + children, height: 1, leaf: true, minX: Infinity, @@ -537,8 +521,7 @@ function createNode(children) { // combines selection algorithm with binary divide & conquer approach function multiSelect(arr, left, right, n, compare) { - var stack = [left, right], - mid; + const stack = [left, right]; while (stack.length) { right = stack.pop(); @@ -546,7 +529,7 @@ function multiSelect(arr, left, right, n, compare) { if (right - left <= n) continue; - mid = left + Math.ceil((right - left) / n / 2) * n; + const mid = left + Math.ceil((right - left) / n / 2) * n; quickselect(arr, mid, left, right, compare); stack.push(left, mid, mid, right); diff --git a/package.json b/package.json index 596f143..62a4fe1 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "devDependencies": { "benchmark": "^2.1.4", "c8": "^4.1.4", - "eslint": "^4.13.1", - "eslint-config-mourner": "^2.0.3", + "eslint": "^5.16.0", + "eslint-config-mourner": "^3.0.0", "esm": "^3.2.22", "rollup": "^1.11.3", "rollup-plugin-node-resolve": "^4.2.3", @@ -34,7 +34,7 @@ "tape": "^4.10.1" }, "scripts": { - "pretest": "eslint index.js test/test.js ", + "pretest": "eslint index.js test/test.js", "test": "tape -r esm test/test.js", "perf": "node -r esm ./bench/perf.js", "cov": "c8 npm run test", @@ -48,10 +48,7 @@ "rbush.min.js" ], "eslintConfig": { - "extends": "mourner", - "parserOptions": { - "sourceType": "module" - } + "extends": "mourner" }, "dependencies": { "quickselect": "^2.0.0" diff --git a/test/test.js b/test/test.js index 6f5d4b2..3d894ee 100644 --- a/test/test.js +++ b/test/test.js @@ -14,24 +14,18 @@ function defaultCompare(a, b) { } function someData(n) { - var data = []; - - for (var i = 0; i < n; i++) { + const data = []; + for (let i = 0; i < n; i++) { data.push({minX: i, minY: i, maxX: i, maxY: i}); } return data; } -function arrToBBox(arr) { - return { - minX: arr[0], - minY: arr[1], - maxX: arr[2], - maxY: arr[3] - }; +function arrToBBox([minX, minY, maxX, maxY]) { + return {minX, minY, maxX, maxY}; } -var data = [[0,0,0,0],[10,10,10,10],[20,20,20,20],[25,0,25,0],[35,10,35,10],[45,20,45,20],[0,25,0,25],[10,35,10,35], +const data = [[0,0,0,0],[10,10,10,10],[20,20,20,20],[25,0,25,0],[35,10,35,10],[45,20,45,20],[0,25,0,25],[10,35,10,35], [20,45,20,45],[25,25,25,25],[35,35,35,35],[45,45,45,45],[50,0,50,0],[60,10,60,10],[70,20,70,20],[75,0,75,0], [85,10,85,10],[95,20,95,20],[50,25,50,25],[60,35,60,35],[70,45,70,45],[75,25,75,25],[85,35,85,35],[95,45,95,45], [0,50,0,50],[10,60,10,60],[20,70,20,70],[25,50,25,50],[35,60,35,60],[45,70,45,70],[0,75,0,75],[10,85,10,85], @@ -39,45 +33,39 @@ var data = [[0,0,0,0],[10,10,10,10],[20,20,20,20],[25,0,25,0],[35,10,35,10],[45, [85,60,85,60],[95,70,95,70],[50,75,50,75],[60,85,60,85],[70,95,70,95],[75,75,75,75],[85,85,85,85],[95,95,95,95]] .map(arrToBBox); -var emptyData = [[-Infinity, -Infinity, Infinity, Infinity],[-Infinity, -Infinity, Infinity, Infinity], +const emptyData = [[-Infinity, -Infinity, Infinity, Infinity],[-Infinity, -Infinity, Infinity, Infinity], [-Infinity, -Infinity, Infinity, Infinity],[-Infinity, -Infinity, Infinity, Infinity], [-Infinity, -Infinity, Infinity, Infinity],[-Infinity, -Infinity, Infinity, Infinity]].map(arrToBBox); -t('constructor accepts a format argument to customize the data format', function (t) { - var tree = new RBush(4, ['.minLng', '.minLat', '.maxLng', '.maxLat']); +t('constructor accepts a format argument to customize the data format', (t) => { + const tree = new RBush(4, ['.minLng', '.minLat', '.maxLng', '.maxLat']); t.same(tree.toBBox({minLng: 1, minLat: 2, maxLng: 3, maxLat: 4}), {minX: 1, minY: 2, maxX: 3, maxY: 4}); t.end(); }); -t('constructor uses 9 max entries by default', function (t) { - var tree = new RBush().load(someData(9)); +t('constructor uses 9 max entries by default', (t) => { + const tree = new RBush().load(someData(9)); t.equal(tree.toJSON().height, 1); - var tree2 = new RBush().load(someData(10)); + const tree2 = new RBush().load(someData(10)); t.equal(tree2.toJSON().height, 2); t.end(); }); -t('#toBBox, #compareMinX, #compareMinY can be overriden to allow custom data structures', function (t) { - - var tree = new RBush(4); - tree.toBBox = function (item) { - return { - minX: item.minLng, - minY: item.minLat, - maxX: item.maxLng, - maxY: item.maxLat - }; - }; - tree.compareMinX = function (a, b) { - return a.minLng - b.minLng; - }; - tree.compareMinY = function (a, b) { - return a.minLat - b.minLat; - }; - - var data = [ +t('#toBBox, #compareMinX, #compareMinY can be overriden to allow custom data structures', (t) => { + + const tree = new RBush(4); + tree.toBBox = item => ({ + minX: item.minLng, + minY: item.minLat, + maxX: item.maxLng, + maxY: item.maxLat + }); + tree.compareMinX = (a, b) => a.minLng - b.minLng; + tree.compareMinY = (a, b) => a.minLat - b.minLat; + + const data = [ {minLng: -115, minLat: 45, maxLng: -105, maxLat: 55}, {minLng: 105, minLat: 45, maxLng: 115, maxLat: 55}, {minLng: 105, minLat: -55, maxLng: 115, maxLat: -45}, @@ -120,21 +108,21 @@ t('#toBBox, #compareMinX, #compareMinY can be overriden to allow custom data str t.end(); }); -t('#load bulk-loads the given data given max node entries and forms a proper search tree', function (t) { +t('#load bulk-loads the given data given max node entries and forms a proper search tree', (t) => { - var tree = new RBush(4).load(data); + const tree = new RBush(4).load(data); sortedEqual(t, tree.all(), data); t.end(); }); -t('#load uses standard insertion when given a low number of items', function (t) { +t('#load uses standard insertion when given a low number of items', (t) => { - var tree = new RBush(8) + const tree = new RBush(8) .load(data) .load(data.slice(0, 3)); - var tree2 = new RBush(8) + const tree2 = new RBush(8) .load(data) .insert(data[0]) .insert(data[1]) @@ -144,15 +132,15 @@ t('#load uses standard insertion when given a low number of items', function (t) t.end(); }); -t('#load does nothing if loading empty data', function (t) { - var tree = new RBush().load([]); +t('#load does nothing if loading empty data', (t) => { + const tree = new RBush().load([]); t.same(tree.toJSON(), new RBush().toJSON()); t.end(); }); -t('#load handles the insertion of maxEntries + 2 empty bboxes', function (t) { - var tree = new RBush(4) +t('#load handles the insertion of maxEntries + 2 empty bboxes', (t) => { + const tree = new RBush(4) .load(emptyData); t.equal(tree.toJSON().height, 2); @@ -161,10 +149,10 @@ t('#load handles the insertion of maxEntries + 2 empty bboxes', function (t) { t.end(); }); -t('#insert handles the insertion of maxEntries + 2 empty bboxes', function (t) { - var tree = new RBush(4); +t('#insert handles the insertion of maxEntries + 2 empty bboxes', (t) => { + const tree = new RBush(4); - emptyData.forEach(function (datum) { + emptyData.forEach((datum) => { tree.insert(datum); }); @@ -174,8 +162,8 @@ t('#insert handles the insertion of maxEntries + 2 empty bboxes', function (t) { t.end(); }); -t('#load properly splits tree root when merging trees of the same height', function (t) { - var tree = new RBush(4) +t('#load properly splits tree root when merging trees of the same height', (t) => { + const tree = new RBush(4) .load(data) .load(data); @@ -185,14 +173,14 @@ t('#load properly splits tree root when merging trees of the same height', funct t.end(); }); -t('#load properly merges data of smaller or bigger tree heights', function (t) { - var smaller = someData(10); +t('#load properly merges data of smaller or bigger tree heights', (t) => { + const smaller = someData(10); - var tree1 = new RBush(4) + const tree1 = new RBush(4) .load(data) .load(smaller); - var tree2 = new RBush(4) + const tree2 = new RBush(4) .load(smaller) .load(data); @@ -204,10 +192,10 @@ t('#load properly merges data of smaller or bigger tree heights', function (t) { t.end(); }); -t('#search finds matching points in the tree given a bbox', function (t) { +t('#search finds matching points in the tree given a bbox', (t) => { - var tree = new RBush(4).load(data); - var result = tree.search({minX: 40, minY: 20, maxX: 80, maxY: 70}); + const tree = new RBush(4).load(data); + const result = tree.search({minX: 40, minY: 20, maxX: 80, maxY: 70}); sortedEqual(t, result, [ [70,20,70,20],[75,25,75,25],[45,45,45,45],[50,50,50,50],[60,60,60,60],[70,70,70,70], @@ -217,34 +205,34 @@ t('#search finds matching points in the tree given a bbox', function (t) { t.end(); }); -t('#collides returns true when search finds matching points', function (t) { +t('#collides returns true when search finds matching points', (t) => { - var tree = new RBush(4).load(data); - var result = tree.collides({minX: 40, minY: 20, maxX: 80, maxY: 70}); + const tree = new RBush(4).load(data); + const result = tree.collides({minX: 40, minY: 20, maxX: 80, maxY: 70}); t.same(result, true); t.end(); }); -t('#search returns an empty array if nothing found', function (t) { - var result = new RBush(4).load(data).search([200, 200, 210, 210]); +t('#search returns an empty array if nothing found', (t) => { + const result = new RBush(4).load(data).search([200, 200, 210, 210]); t.same(result, []); t.end(); }); -t('#collides returns false if nothing found', function (t) { - var result = new RBush(4).load(data).collides([200, 200, 210, 210]); +t('#collides returns false if nothing found', (t) => { + const result = new RBush(4).load(data).collides([200, 200, 210, 210]); t.same(result, false); t.end(); }); -t('#all returns all points in the tree', function (t) { +t('#all returns all points in the tree', (t) => { - var tree = new RBush(4).load(data); - var result = tree.all(); + const tree = new RBush(4).load(data); + const result = tree.all(); sortedEqual(t, result, data); sortedEqual(t, tree.search({minX: 0, minY: 0, maxX: 100, maxY: 100}), data); @@ -252,17 +240,17 @@ t('#all returns all points in the tree', function (t) { t.end(); }); -t('#toJSON & #fromJSON exports and imports search tree in JSON format', function (t) { +t('#toJSON & #fromJSON exports and imports search tree in JSON format', (t) => { - var tree = new RBush(4).load(data); - var tree2 = new RBush(4).fromJSON(tree.data); + const tree = new RBush(4).load(data); + const tree2 = new RBush(4).fromJSON(tree.data); sortedEqual(t, tree.all(), tree2.all()); t.end(); }); -t('#insert adds an item to an existing tree correctly', function (t) { - var items = [ +t('#insert adds an item to an existing tree correctly', (t) => { + const items = [ [0, 0, 0, 0], [1, 1, 1, 1], [2, 2, 2, 2], @@ -270,7 +258,7 @@ t('#insert adds an item to an existing tree correctly', function (t) { [1, 1, 2, 2] ].map(arrToBBox); - var tree = new RBush(4).load(items.slice(0, 3)); + const tree = new RBush(4).load(items.slice(0, 3)); tree.insert(items[3]); t.equal(tree.toJSON().height, 1); @@ -283,21 +271,21 @@ t('#insert adds an item to an existing tree correctly', function (t) { t.end(); }); -t('#insert does nothing if given undefined', function (t) { +t('#insert does nothing if given undefined', (t) => { t.same( new RBush().load(data), new RBush().load(data).insert()); t.end(); }); -t('#insert forms a valid tree if items are inserted one by one', function (t) { - var tree = new RBush(4); +t('#insert forms a valid tree if items are inserted one by one', (t) => { + const tree = new RBush(4); - for (var i = 0; i < data.length; i++) { + for (let i = 0; i < data.length; i++) { tree.insert(data[i]); } - var tree2 = new RBush(4).load(data); + const tree2 = new RBush(4).load(data); t.ok(tree.toJSON().height - tree2.toJSON().height <= 1); @@ -305,10 +293,10 @@ t('#insert forms a valid tree if items are inserted one by one', function (t) { t.end(); }); -t('#remove removes items correctly', function (t) { - var tree = new RBush(4).load(data); +t('#remove removes items correctly', (t) => { + const tree = new RBush(4).load(data); - var len = data.length; + const len = data.length; tree.remove(data[0]); tree.remove(data[1]); @@ -323,51 +311,49 @@ t('#remove removes items correctly', function (t) { tree.all()); t.end(); }); -t('#remove does nothing if nothing found', function (t) { +t('#remove does nothing if nothing found', (t) => { t.same( new RBush().load(data), new RBush().load(data).remove([13, 13, 13, 13])); t.end(); }); -t('#remove does nothing if given undefined', function (t) { +t('#remove does nothing if given undefined', (t) => { t.same( new RBush().load(data), new RBush().load(data).remove()); t.end(); }); -t('#remove brings the tree to a clear state when removing everything one by one', function (t) { - var tree = new RBush(4).load(data); +t('#remove brings the tree to a clear state when removing everything one by one', (t) => { + const tree = new RBush(4).load(data); - for (var i = 0; i < data.length; i++) { + for (let i = 0; i < data.length; i++) { tree.remove(data[i]); } t.same(tree.toJSON(), new RBush(4).toJSON()); t.end(); }); -t('#remove accepts an equals function', function (t) { - var tree = new RBush(4).load(data); +t('#remove accepts an equals function', (t) => { + const tree = new RBush(4).load(data); - var item = {minX: 20, minY: 70, maxX: 20, maxY: 70, foo: 'bar'}; + const item = {minX: 20, minY: 70, maxX: 20, maxY: 70, foo: 'bar'}; tree.insert(item); - tree.remove(JSON.parse(JSON.stringify(item)), function (a, b) { - return a.foo === b.foo; - }); + tree.remove(JSON.parse(JSON.stringify(item)), (a, b) => a.foo === b.foo); sortedEqual(t, tree.all(), data); t.end(); }); -t('#clear should clear all the data in the tree', function (t) { +t('#clear should clear all the data in the tree', (t) => { t.same( new RBush(4).load(data).clear().toJSON(), new RBush(4).toJSON()); t.end(); }); -t('should have chainable API', function (t) { - t.doesNotThrow(function () { +t('should have chainable API', (t) => { + t.doesNotThrow(() => { new RBush() .load(data) .insert(data[0]) From 674711334468326dbe5433c3e9c537a744c805a6 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 10 May 2019 19:11:15 +0300 Subject: [PATCH 3/4] transpile UMD bundles with buble --- package.json | 1 + rollup.config.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 62a4fe1..4731ad5 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "eslint-config-mourner": "^3.0.0", "esm": "^3.2.22", "rollup": "^1.11.3", + "rollup-plugin-buble": "^0.19.6", "rollup-plugin-node-resolve": "^4.2.3", "rollup-plugin-terser": "^4.0.4", "tape": "^4.10.1" diff --git a/rollup.config.js b/rollup.config.js index 159fd74..2651ff5 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,17 +1,19 @@ import {terser} from 'rollup-plugin-terser'; import resolve from 'rollup-plugin-node-resolve'; +import buble from 'rollup-plugin-buble'; const output = (file, plugins) => ({ input: 'index.js', output: { name: 'rbush', format: 'umd', + indent: false, file }, plugins }); export default [ - output('rbush.js', [resolve()]), - output('rbush.min.js', [resolve(), terser()]) + output('rbush.js', [resolve(), buble()]), + output('rbush.min.js', [resolve(), buble(), terser()]) ]; From 8207dab19950dc596a528fa30c7642a4fbb1e877 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 10 May 2019 19:27:37 +0300 Subject: [PATCH 4/4] remove format option and update readme --- README.md | 40 ++++++++++++++++++++++------------------ index.js | 29 ++--------------------------- test/test.js | 20 ++++++++++++++++++-- 3 files changed, 42 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index e3c331a..9176397 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,15 @@ Install with NPM (`npm install rbush`), or use CDN links for browsers: ### Creating a Tree ```js -var tree = rbush(); +const tree = new RBush(); ``` -An optional argument to `rbush` defines the maximum number of entries in a tree node. +An optional argument to `RBush` defines the maximum number of entries in a tree node. `9` (used by default) is a reasonable choice for most applications. Higher value means faster insertion and slower search, and vice versa. ```js -var tree = rbush(16); +const tree = new RBush(16); ``` ### Adding Data @@ -49,7 +49,7 @@ var tree = rbush(16); Insert an item: ```js -var item = { +const item = { minX: 20, minY: 40, maxX: 30, @@ -72,7 +72,7 @@ However, you can pass a custom `equals` function to compare by value for removal which is useful when you only have a copy of the object you need removed (e.g. loaded from server): ```js -tree.remove(itemCopy, function (a, b) { +tree.remove(itemCopy, (a, b) => { return a.id === b.id; }); ``` @@ -87,12 +87,16 @@ tree.clear(); By default, RBush assumes the format of data points to be an object with `minX`, `minY`, `maxX` and `maxY` properties. -You can customize this by providing an array with corresponding accessor strings -as a second argument to `rbush` like this: +You can customize this by overriding `toBBox`, `compareMinX` and `compareMinY` methods like this: ```js -var tree = rbush(9, ['[0]', '[1]', '[0]', '[1]']); // accept [x, y] points -tree.insert([20, 50]); +class MyRBush extends RBush { + toBBox([x, y]) { return {minX: x, minY: y, maxX: x, maxY: y}; } + compareMinX(a, b) { return a.x - b.x; } + compareMinY(a, b) { return a.y - b.y; } +} +const tree = new MyRBush(); +tree.insert([20, 50]); // accepts [x, y] points ``` If you're indexing a static list of points (you don't need to add/remove points after indexing), you should use [kdbush](https://github.com/mourner/kdbush) which performs point indexing 5-8x faster than RBush. @@ -119,7 +123,7 @@ but makes query performance worse if the data is scattered. ### Search ```js -var result = tree.search({ +const result = tree.search({ minX: 40, minY: 20, maxX: 80, @@ -130,10 +134,10 @@ var result = tree.search({ Returns an array of data items (points or rectangles) that the given bounding box intersects. Note that the `search` method accepts a bounding box in `{minX, minY, maxX, maxY}` format -regardless of the format specified in the constructor (which only affects inserted objects). +regardless of the data format. ```js -var allItems = tree.all(); +const allItems = tree.all(); ``` Returns all items of the tree. @@ -141,7 +145,7 @@ Returns all items of the tree. ### Collisions ```js -var result = tree.collides({minX: 40, minY: 20, maxX: 80, maxY: 70}); +const result = tree.collides({minX: 40, minY: 20, maxX: 80, maxY: 70}); ``` Returns `true` if there are any items intersecting the given bounding box, otherwise `false`. @@ -151,10 +155,10 @@ Returns `true` if there are any items intersecting the given bounding box, other ```js // export data as JSON object -var treeData = tree.toJSON(); +const treeData = tree.toJSON(); // import previously exported data -var tree = rbush(9).fromJSON(treeData); +const tree = rbush(9).fromJSON(treeData); ``` Importing and exporting as JSON allows you to use RBush on both the server (using Node.js) and the browser combined, @@ -204,14 +208,14 @@ bulk-insert 1M items | 1.25s | n/a | 6.7x ```bash npm install # install dependencies -npm test # check the code with JSHint and run tests +npm test # lint the code and run tests npm run perf # run performance benchmarks -npm run cov # report test coverage (with more detailed report in coverage/lcov-report/index.html) +npm run cov # report test coverage ``` ## Compatibility -RBush should run on Node and all major browsers. The only caveat: IE 8 needs an [Array#indexOf polyfill](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill) for `remove` method to work. +RBush should run on Node and all major browsers that support ES5. ## Changelog diff --git a/index.js b/index.js index 9ac1836..2b52dd6 100644 --- a/index.js +++ b/index.js @@ -1,15 +1,10 @@ import quickselect from 'quickselect'; export default class RBush { - constructor(maxEntries, format) { + constructor(maxEntries = 9) { // max entries in a node is 9 by default; min node fill is 40% for best performance - this._maxEntries = Math.max(4, maxEntries || 9); + this._maxEntries = Math.max(4, maxEntries); this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); - - if (format) { - this._initFormat(format); - } - this.clear(); } @@ -410,26 +405,6 @@ export default class RBush { } else calcBBox(path[i], this.toBBox); } } - - _initFormat(format) { - // data format (minX, minY, maxX, maxY accessors) - - // uses eval-type function compilation instead of just accepting a toBBox function - // because the algorithms are very sensitive to sorting functions performance, - // so they should be dead simple and without inner calls - - const compareArr = ['return a', ' - b', ';']; - - this.compareMinX = new Function('a', 'b', compareArr.join(format[0])); - this.compareMinY = new Function('a', 'b', compareArr.join(format[1])); - - this.toBBox = new Function('a', `return { - minX: a${format[0]}, - minY: a${format[1]}, - maxX: a${format[2]}, - maxY: a${format[3]} - };`); - } } function findItem(item, items, equalsFn) { diff --git a/test/test.js b/test/test.js index 3d894ee..66065cb 100644 --- a/test/test.js +++ b/test/test.js @@ -37,8 +37,24 @@ const emptyData = [[-Infinity, -Infinity, Infinity, Infinity],[-Infinity, -Infin [-Infinity, -Infinity, Infinity, Infinity],[-Infinity, -Infinity, Infinity, Infinity], [-Infinity, -Infinity, Infinity, Infinity],[-Infinity, -Infinity, Infinity, Infinity]].map(arrToBBox); -t('constructor accepts a format argument to customize the data format', (t) => { - const tree = new RBush(4, ['.minLng', '.minLat', '.maxLng', '.maxLat']); +t('allows custom formats by overriding some methods', (t) => { + class MyRBush extends RBush { + toBBox(a) { + return { + minX: a.minLng, + minY: a.minLat, + maxX: a.maxLng, + maxY: a.maxLat + }; + } + compareMinX(a, b) { + return a.minLng - b.minLng; + } + compareMinY(a, b) { + return a.minLat - b.minLat; + } + } + const tree = new MyRBush(4); t.same(tree.toBBox({minLng: 1, minLat: 2, maxLng: 3, maxLat: 4}), {minX: 1, minY: 2, maxX: 3, maxY: 4}); t.end();