leaflet.layergroup.collision
Version:
Leaflet plugin for uncluttering L.Markers using basic collision detection.
379 lines (291 loc) • 11.5 kB
JavaScript
'use strict';
/*eslint key-spacing: 0, comma-spacing: 0, quotes: 0*/
var rbush = require('../rbush.js'),
t = require('tape');
function sortedEqual(t, a, b, compare) {
t.same(a.slice().sort(compare), b.slice().sort(compare));
}
function someData(n) {
var data = [];
for (var i = 0; i < n; i++) {
data.push([i, i, i, i]);
}
return data;
}
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],
[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],
[20,95,20,95],[25,75,25,75],[35,85,35,85],[45,95,45,95],[50,50,50,50],[60,60,60,60],[70,70,70,70],[75,50,75,50],
[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]];
var 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]];
var testTree = {"children":[{
"children":[
{"children":[[0,0,0,0],[10,10,10,10],[20,20,20,20],[25,0,25,0]],"height":1,"bbox":[0,0,25,20],"leaf":true},
{"children":[[0,25,0,25],[10,35,10,35],[25,25,25,25],[20,45,20,45]],"height":1,"bbox":[0,25,25,45],"leaf":true},
{"children":[[50,0,50,0],[35,10,35,10],[60,10,60,10],[45,20,45,20]],"height":1,"bbox":[35,0,60,20],"leaf":true},
{"children":[[50,25,50,25],[35,35,35,35],[60,35,60,35],[45,45,45,45]],"height":1,"bbox":[35,25,60,45],"leaf":true}
],"height":2,"bbox":[0,0,60,45]
},{
"children":[
{"children":[[0,50,0,50],[25,50,25,50],[10,60,10,60],[20,70,20,70]],"height":1,"bbox":[0,50,25,70],"leaf":true},
{"children":[[0,75,0,75],[25,75,25,75],[10,85,10,85],[20,95,20,95]],"height":1,"bbox":[0,75,25,95],"leaf":true},
{"children":[[35,60,35,60],[50,50,50,50],[60,60,60,60],[45,70,45,70]],"height":1,"bbox":[35,50,60,70],"leaf":true},
{"children":[[50,75,50,75],[60,85,60,85],[45,95,45,95],[35,85,35,85]],"height":1,"bbox":[35,75,60,95],"leaf":true}
],"height":2,"bbox":[0,50,60,95]
},{
"children":[
{"children":[[70,20,70,20],[75,0,75,0],[75,25,75,25],[70,45,70,45]],"height":1,"bbox":[70,0,75,45],"leaf":true},
{"children":[[75,50,75,50],[70,70,70,70],[75,75,75,75],[70,95,70,95]],"height":1,"bbox":[70,50,75,95],"leaf":true},
{"children":[[85,10,85,10],[95,20,95,20],[85,35,85,35],[95,45,95,45]],"height":1,"bbox":[85,10,95,45],"leaf":true},
{"children":[[85,60,85,60],[95,70,95,70],[85,85,85,85],[95,95,95,95]],"height":1,"bbox":[85,60,95,95],"leaf":true}
],"height":2,"bbox":[70,0,95,95]
}],"height":3,"bbox":[0,0,95,95]};
t('constructor accepts a format argument to customize the data format', function (t) {
var tree = rbush(4, ['.minLng', '.minLat', '.maxLng', '.maxLat']);
t.same(tree.toBBox({minLng: 1, minLat: 2, maxLng: 3, maxLat: 4}), [1, 2, 3, 4]);
t.end();
});
t('constructor uses 9 max entries by default', function (t) {
var tree = rbush().load(someData(9));
t.equal(tree.toJSON().height, 1);
var tree2 = 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);
tree.toBBox = function (item) {
return [item.minLng, item.minLat, item.maxLng, item.maxLat];
};
tree.compareMinX = function (a, b) {
return a.minLng - b.minLng;
};
tree.compareMinY = function (a, b) {
return a.minLat - b.minLat;
};
var 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},
{minLng: -115, minLat: -55, maxLng: -105, maxLat: -45}
];
tree.load(data);
function byLngLat(a, b) {
return a.minLng - b.minLng || a.minLat - b.minLat;
}
sortedEqual(t, tree.search([-180, -90, 180, 90]), [
{minLng: -115, minLat: 45, maxLng: -105, maxLat: 55},
{minLng: 105, minLat: 45, maxLng: 115, maxLat: 55},
{minLng: 105, minLat: -55, maxLng: 115, maxLat: -45},
{minLng: -115, minLat: -55, maxLng: -105, maxLat: -45}
], byLngLat);
sortedEqual(t, tree.search([-180, -90, 0, 90]), [
{minLng: -115, minLat: 45, maxLng: -105, maxLat: 55},
{minLng: -115, minLat: -55, maxLng: -105, maxLat: -45}
], byLngLat);
sortedEqual(t, tree.search([0, -90, 180, 90]), [
{minLng: 105, minLat: 45, maxLng: 115, maxLat: 55},
{minLng: 105, minLat: -55, maxLng: 115, maxLat: -45}
], byLngLat);
sortedEqual(t, tree.search([-180, 0, 180, 90]), [
{minLng: -115, minLat: 45, maxLng: -105, maxLat: 55},
{minLng: 105, minLat: 45, maxLng: 115, maxLat: 55}
], byLngLat);
sortedEqual(t, tree.search([-180, -90, 180, 0]), [
{minLng: 105, minLat: -55, maxLng: 115, maxLat: -45},
{minLng: -115, minLat: -55, maxLng: -105, maxLat: -45}
], byLngLat);
t.end();
});
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);
sortedEqual(t, tree.all(), rbush(4).fromJSON(testTree).all());
t.end();
});
t('#load uses standard insertion when given a low number of items', function (t) {
var tree = rbush(8)
.load(data)
.load(data.slice(0, 3));
var tree2 = rbush(8)
.load(data)
.insert(data[0])
.insert(data[1])
.insert(data[2]);
t.same(tree.toJSON(), tree2.toJSON());
t.end();
});
t('#load does nothing if loading empty data', function (t) {
var tree = rbush().load([]);
t.same(tree.toJSON(), rbush().toJSON());
t.end();
});
t('#load handles the insertion of maxEntries + 2 empty bboxes', function (t) {
var tree = rbush(4)
.load(emptyData);
t.equal(tree.toJSON().height, 2);
sortedEqual(t, tree.all(), emptyData);
t.end();
});
t('#insert handles the insertion of maxEntries + 2 empty bboxes', function (t) {
var tree = rbush(4);
emptyData.forEach(function (datum) {
tree.insert(datum);
});
t.equal(tree.toJSON().height, 2);
sortedEqual(t, tree.all(), emptyData);
t.end();
});
t('#load properly splits tree root when merging trees of the same height', function (t) {
var tree = rbush(4)
.load(data)
.load(data);
t.equal(tree.toJSON().height, 4);
sortedEqual(t, tree.all(), data.concat(data));
t.end();
});
t('#load properly merges data of smaller or bigger tree heights', function (t) {
var smaller = someData(10);
var tree1 = rbush(4)
.load(data)
.load(smaller);
var tree2 = rbush(4)
.load(smaller)
.load(data);
t.equal(tree1.toJSON().height, tree2.toJSON().height);
sortedEqual(t, tree1.all(), data.concat(smaller));
sortedEqual(t, tree2.all(), data.concat(smaller));
t.end();
});
t('#search finds matching points in the tree given a bbox', function (t) {
var tree = rbush(4).load(data);
var result = tree.search([40, 20, 80, 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],
[45,20,45,20],[45,70,45,70],[75,50,75,50],[50,25,50,25],[60,35,60,35],[70,45,70,45]
]);
t.end();
});
t('#collides returns true when search finds matching points', function (t) {
var tree = rbush(4).load(data);
var result = tree.collides([40, 20, 80, 70]);
t.same(result, true);
t.end();
});
t('#search returns an empty array if nothing found', function (t) {
var result = 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]);
t.same(result, false);
t.end();
});
t('#all returns all points in the tree', function (t) {
var tree = rbush(4).load(data);
var result = tree.all();
sortedEqual(t, result, data);
sortedEqual(t, tree.search([0, 0, 100, 100]), data);
t.end();
});
t('#toJSON & #fromJSON exports and imports search tree in JSON format', function (t) {
var tree = rbush(4);
tree.fromJSON(testTree);
var tree2 = rbush(4).load(data);
sortedEqual(t, tree.all(), tree2.all());
t.end();
});
t('#insert adds an item to an existing tree correctly', function (t) {
var tree = rbush(4).load([
[0, 0, 0, 0],
[1, 1, 1, 1],
[2, 2, 2, 2]
]);
tree.insert([3, 3, 3, 3]);
t.same(tree.toJSON(), {
'children':[[0,0,0,0],[1,1,1,1],[2,2,2,2],[3,3,3,3]],
'leaf':true,
'height':1,
'bbox':[0,0,3,3]
});
tree.insert([1, 1, 2, 2]);
t.same(tree.toJSON(), {
'children':[
{'children':[[0,0,0,0],[1,1,1,1]],'leaf':true,'height':1,'bbox':[0,0,1,1]},
{'children':[[1,1,2,2],[2,2,2,2],[3,3,3,3]],'height':1,'leaf':true,'bbox':[1,1,3,3]}
],
'height':2,
'bbox':[0,0,3,3],
'leaf':false
});
t.end();
});
t('#insert does nothing if given undefined', function (t) {
t.same(
rbush().load(data),
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);
for (var i = 0; i < data.length; i++) {
tree.insert(data[i]);
}
var tree2 = rbush(4).load(data);
t.ok(tree.toJSON().height - tree2.toJSON().height <= 1);
sortedEqual(t, tree.all(), tree2.all());
t.end();
});
t('#remove removes items correctly', function (t) {
var tree = rbush(4).load(data);
var len = data.length;
tree.remove(data[0]);
tree.remove(data[1]);
tree.remove(data[2]);
tree.remove(data[len - 1]);
tree.remove(data[len - 2]);
tree.remove(data[len - 3]);
sortedEqual(t,
data.slice(3, len - 3),
tree.all());
t.end();
});
t('#remove does nothing if nothing found', function (t) {
t.same(
rbush().load(data),
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());
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);
for (var i = 0; i < data.length; i++) {
tree.remove(data[i]);
}
t.same(tree.toJSON(), rbush(4).toJSON());
t.end();
});
t('#clear should clear all the data in the tree', function (t) {
t.same(
rbush(4).load(data).clear().toJSON(),
rbush(4).toJSON());
t.end();
});
t('should have chainable API', function (t) {
t.doesNotThrow(function () {
rbush()
.load(data)
.insert(data[0])
.remove(data[0])
.fromJSON(testTree);
});
t.end();
});