foliage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +9 -0
- data/app/assets/images/.keep +0 -0
- data/app/assets/images/map/marker/icon-2x.png +0 -0
- data/app/assets/images/map/marker/icon.png +0 -0
- data/app/assets/images/map/marker/icon.svg +67 -0
- data/app/assets/images/map/marker/shadow.png +0 -0
- data/app/assets/javascripts/core_ext.js.coffee +61 -0
- data/app/assets/javascripts/foliage.js.coffee +23 -0
- data/app/assets/javascripts/foliage/band.js.coffee +99 -0
- data/app/assets/javascripts/foliage/bubbles.js.coffee +77 -0
- data/app/assets/javascripts/foliage/categories.js.coffee +70 -0
- data/app/assets/javascripts/foliage/choropleth.js.coffee +51 -0
- data/app/assets/javascripts/foliage/color.js.coffee +39 -0
- data/app/assets/javascripts/foliage/gradient.js.coffee +72 -0
- data/app/assets/javascripts/foliage/heatmap.js.coffee +49 -0
- data/app/assets/javascripts/foliage/leaf.js.coffee +422 -0
- data/app/assets/javascripts/foliage/path.js.coffee +76 -0
- data/app/assets/javascripts/foliage/paths.js.coffee +131 -0
- data/app/assets/javascripts/foliage/point_group.js.coffee +83 -0
- data/app/assets/javascripts/foliage/points.js.coffee +79 -0
- data/app/assets/javascripts/foliage/simple.js.coffee +35 -0
- data/app/assets/javascripts/leaflet/geographic_util.js.coffee +23 -0
- data/app/assets/javascripts/leaflet/ghost_label.js.coffee +100 -0
- data/app/assets/javascripts/leaflet/ghost_label_cluster.js.coffee +192 -0
- data/app/assets/javascripts/leaflet/layers_scheduler.js.coffee +57 -0
- data/app/assets/javascripts/leaflet/reactive_measure.js.coffee +414 -0
- data/app/assets/stylesheets/all.scss +16 -0
- data/app/assets/stylesheets/application.css +15 -0
- data/app/assets/stylesheets/compass/reset.scss +3 -0
- data/app/assets/stylesheets/compass/reset/utilities.scss +142 -0
- data/app/assets/stylesheets/leaflet.scss +1093 -0
- data/app/assets/stylesheets/leaflet/label.scss +40 -0
- data/app/assets/stylesheets/leaflet/tooltip.scss +42 -0
- data/app/assets/stylesheets/mixins.scss +131 -0
- data/app/assets/stylesheets/reset.scss +89 -0
- data/app/assets/stylesheets/variables.scss +47 -0
- data/app/helpers/foliage_helper.rb +23 -0
- data/lib/foliage.rb +9 -0
- data/lib/foliage/leaf.rb +235 -0
- data/lib/foliage/rails.rb +2 -0
- data/lib/foliage/rails/engine.rb +7 -0
- data/lib/foliage/rails/integration.rb +8 -0
- data/lib/foliage/version.rb +3 -0
- data/vendor/assets/javascripts/.keep +0 -0
- data/vendor/assets/javascripts/autosize.js +211 -0
- data/vendor/assets/javascripts/geographiclib.js +3074 -0
- data/vendor/assets/javascripts/leaflet.js.erb +9175 -0
- data/vendor/assets/javascripts/leaflet/draw.js +3573 -0
- data/vendor/assets/javascripts/leaflet/easy-button.js +366 -0
- data/vendor/assets/javascripts/leaflet/fullscreen.js +162 -0
- data/vendor/assets/javascripts/leaflet/heatmap.js +142 -0
- data/vendor/assets/javascripts/leaflet/label.js +545 -0
- data/vendor/assets/javascripts/leaflet/measure.js +6966 -0
- data/vendor/assets/javascripts/leaflet/modal.js +364 -0
- data/vendor/assets/javascripts/leaflet/providers.js +479 -0
- data/vendor/assets/javascripts/rbush.js +621 -0
- data/vendor/assets/stylesheets/.keep +0 -0
- data/vendor/assets/stylesheets/bootstrap/mixins.scss +55 -0
- data/vendor/assets/stylesheets/bootstrap/variables.scss +10 -0
- data/vendor/assets/stylesheets/leaflet.scss +479 -0
- data/vendor/assets/stylesheets/leaflet/draw.scss +282 -0
- data/vendor/assets/stylesheets/leaflet/easy-button.scss +56 -0
- data/vendor/assets/stylesheets/leaflet/fullscreen.scss +2 -0
- data/vendor/assets/stylesheets/leaflet/measure.scss +168 -0
- data/vendor/assets/stylesheets/leaflet/modal.scss +85 -0
- metadata +171 -0
@@ -0,0 +1,621 @@
|
|
1
|
+
/*
|
2
|
+
(c) 2015, Vladimir Agafonkin
|
3
|
+
RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles.
|
4
|
+
https://github.com/mourner/rbush
|
5
|
+
*/
|
6
|
+
|
7
|
+
(function () {
|
8
|
+
'use strict';
|
9
|
+
|
10
|
+
function rbush(maxEntries, format) {
|
11
|
+
|
12
|
+
// jshint newcap: false, validthis: true
|
13
|
+
if (!(this instanceof rbush)) return new rbush(maxEntries, format);
|
14
|
+
|
15
|
+
// max entries in a node is 9 by default; min node fill is 40% for best performance
|
16
|
+
this._maxEntries = Math.max(4, maxEntries || 9);
|
17
|
+
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
|
18
|
+
|
19
|
+
if (format) {
|
20
|
+
this._initFormat(format);
|
21
|
+
}
|
22
|
+
|
23
|
+
this.clear();
|
24
|
+
}
|
25
|
+
|
26
|
+
rbush.prototype = {
|
27
|
+
|
28
|
+
all: function () {
|
29
|
+
return this._all(this.data, []);
|
30
|
+
},
|
31
|
+
|
32
|
+
search: function (bbox) {
|
33
|
+
|
34
|
+
var node = this.data,
|
35
|
+
result = [],
|
36
|
+
toBBox = this.toBBox;
|
37
|
+
|
38
|
+
if (!intersects(bbox, node.bbox)) return result;
|
39
|
+
|
40
|
+
var nodesToSearch = [],
|
41
|
+
i, len, child, childBBox;
|
42
|
+
|
43
|
+
while (node) {
|
44
|
+
for (i = 0, len = node.children.length; i < len; i++) {
|
45
|
+
|
46
|
+
child = node.children[i];
|
47
|
+
childBBox = node.leaf ? toBBox(child) : child.bbox;
|
48
|
+
|
49
|
+
if (intersects(bbox, childBBox)) {
|
50
|
+
if (node.leaf) result.push(child);
|
51
|
+
else if (contains(bbox, childBBox)) this._all(child, result);
|
52
|
+
else nodesToSearch.push(child);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
node = nodesToSearch.pop();
|
56
|
+
}
|
57
|
+
|
58
|
+
return result;
|
59
|
+
},
|
60
|
+
|
61
|
+
collides: function (bbox) {
|
62
|
+
|
63
|
+
var node = this.data,
|
64
|
+
toBBox = this.toBBox;
|
65
|
+
|
66
|
+
if (!intersects(bbox, node.bbox)) return false;
|
67
|
+
|
68
|
+
var nodesToSearch = [],
|
69
|
+
i, len, child, childBBox;
|
70
|
+
|
71
|
+
while (node) {
|
72
|
+
for (i = 0, len = node.children.length; i < len; i++) {
|
73
|
+
|
74
|
+
child = node.children[i];
|
75
|
+
childBBox = node.leaf ? toBBox(child) : child.bbox;
|
76
|
+
|
77
|
+
if (intersects(bbox, childBBox)) {
|
78
|
+
if (node.leaf || contains(bbox, childBBox)) return true;
|
79
|
+
nodesToSearch.push(child);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
node = nodesToSearch.pop();
|
83
|
+
}
|
84
|
+
|
85
|
+
return false;
|
86
|
+
},
|
87
|
+
|
88
|
+
load: function (data) {
|
89
|
+
if (!(data && data.length)) return this;
|
90
|
+
|
91
|
+
if (data.length < this._minEntries) {
|
92
|
+
for (var i = 0, len = data.length; i < len; i++) {
|
93
|
+
this.insert(data[i]);
|
94
|
+
}
|
95
|
+
return this;
|
96
|
+
}
|
97
|
+
|
98
|
+
// recursively build the tree with the given data from stratch using OMT algorithm
|
99
|
+
var node = this._build(data.slice(), 0, data.length - 1, 0);
|
100
|
+
|
101
|
+
if (!this.data.children.length) {
|
102
|
+
// save as is if tree is empty
|
103
|
+
this.data = node;
|
104
|
+
|
105
|
+
} else if (this.data.height === node.height) {
|
106
|
+
// split root if trees have the same height
|
107
|
+
this._splitRoot(this.data, node);
|
108
|
+
|
109
|
+
} else {
|
110
|
+
if (this.data.height < node.height) {
|
111
|
+
// swap trees if inserted one is bigger
|
112
|
+
var tmpNode = this.data;
|
113
|
+
this.data = node;
|
114
|
+
node = tmpNode;
|
115
|
+
}
|
116
|
+
|
117
|
+
// insert the small tree into the large tree at appropriate level
|
118
|
+
this._insert(node, this.data.height - node.height - 1, true);
|
119
|
+
}
|
120
|
+
|
121
|
+
return this;
|
122
|
+
},
|
123
|
+
|
124
|
+
insert: function (item) {
|
125
|
+
if (item) this._insert(item, this.data.height - 1);
|
126
|
+
return this;
|
127
|
+
},
|
128
|
+
|
129
|
+
clear: function () {
|
130
|
+
this.data = {
|
131
|
+
children: [],
|
132
|
+
height: 1,
|
133
|
+
bbox: empty(),
|
134
|
+
leaf: true
|
135
|
+
};
|
136
|
+
return this;
|
137
|
+
},
|
138
|
+
|
139
|
+
remove: function (item) {
|
140
|
+
if (!item) return this;
|
141
|
+
|
142
|
+
var node = this.data,
|
143
|
+
bbox = this.toBBox(item),
|
144
|
+
path = [],
|
145
|
+
indexes = [],
|
146
|
+
i, parent, index, goingUp;
|
147
|
+
|
148
|
+
// depth-first iterative tree traversal
|
149
|
+
while (node || path.length) {
|
150
|
+
|
151
|
+
if (!node) { // go up
|
152
|
+
node = path.pop();
|
153
|
+
parent = path[path.length - 1];
|
154
|
+
i = indexes.pop();
|
155
|
+
goingUp = true;
|
156
|
+
}
|
157
|
+
|
158
|
+
if (node.leaf) { // check current node
|
159
|
+
index = node.children.indexOf(item);
|
160
|
+
|
161
|
+
if (index !== -1) {
|
162
|
+
// item found, remove the item and condense tree upwards
|
163
|
+
node.children.splice(index, 1);
|
164
|
+
path.push(node);
|
165
|
+
this._condense(path);
|
166
|
+
return this;
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
if (!goingUp && !node.leaf && contains(node.bbox, bbox)) { // go down
|
171
|
+
path.push(node);
|
172
|
+
indexes.push(i);
|
173
|
+
i = 0;
|
174
|
+
parent = node;
|
175
|
+
node = node.children[0];
|
176
|
+
|
177
|
+
} else if (parent) { // go right
|
178
|
+
i++;
|
179
|
+
node = parent.children[i];
|
180
|
+
goingUp = false;
|
181
|
+
|
182
|
+
} else node = null; // nothing found
|
183
|
+
}
|
184
|
+
|
185
|
+
return this;
|
186
|
+
},
|
187
|
+
|
188
|
+
toBBox: function (item) { return item; },
|
189
|
+
|
190
|
+
compareMinX: function (a, b) { return a[0] - b[0]; },
|
191
|
+
compareMinY: function (a, b) { return a[1] - b[1]; },
|
192
|
+
|
193
|
+
toJSON: function () { return this.data; },
|
194
|
+
|
195
|
+
fromJSON: function (data) {
|
196
|
+
this.data = data;
|
197
|
+
return this;
|
198
|
+
},
|
199
|
+
|
200
|
+
_all: function (node, result) {
|
201
|
+
var nodesToSearch = [];
|
202
|
+
while (node) {
|
203
|
+
if (node.leaf) result.push.apply(result, node.children);
|
204
|
+
else nodesToSearch.push.apply(nodesToSearch, node.children);
|
205
|
+
|
206
|
+
node = nodesToSearch.pop();
|
207
|
+
}
|
208
|
+
return result;
|
209
|
+
},
|
210
|
+
|
211
|
+
_build: function (items, left, right, height) {
|
212
|
+
|
213
|
+
var N = right - left + 1,
|
214
|
+
M = this._maxEntries,
|
215
|
+
node;
|
216
|
+
|
217
|
+
if (N <= M) {
|
218
|
+
// reached leaf level; return leaf
|
219
|
+
node = {
|
220
|
+
children: items.slice(left, right + 1),
|
221
|
+
height: 1,
|
222
|
+
bbox: null,
|
223
|
+
leaf: true
|
224
|
+
};
|
225
|
+
calcBBox(node, this.toBBox);
|
226
|
+
return node;
|
227
|
+
}
|
228
|
+
|
229
|
+
if (!height) {
|
230
|
+
// target height of the bulk-loaded tree
|
231
|
+
height = Math.ceil(Math.log(N) / Math.log(M));
|
232
|
+
|
233
|
+
// target number of root entries to maximize storage utilization
|
234
|
+
M = Math.ceil(N / Math.pow(M, height - 1));
|
235
|
+
}
|
236
|
+
|
237
|
+
node = {
|
238
|
+
children: [],
|
239
|
+
height: height,
|
240
|
+
bbox: null,
|
241
|
+
leaf: false
|
242
|
+
};
|
243
|
+
|
244
|
+
// split the items into M mostly square tiles
|
245
|
+
|
246
|
+
var N2 = Math.ceil(N / M),
|
247
|
+
N1 = N2 * Math.ceil(Math.sqrt(M)),
|
248
|
+
i, j, right2, right3;
|
249
|
+
|
250
|
+
multiSelect(items, left, right, N1, this.compareMinX);
|
251
|
+
|
252
|
+
for (i = left; i <= right; i += N1) {
|
253
|
+
|
254
|
+
right2 = Math.min(i + N1 - 1, right);
|
255
|
+
|
256
|
+
multiSelect(items, i, right2, N2, this.compareMinY);
|
257
|
+
|
258
|
+
for (j = i; j <= right2; j += N2) {
|
259
|
+
|
260
|
+
right3 = Math.min(j + N2 - 1, right2);
|
261
|
+
|
262
|
+
// pack each entry recursively
|
263
|
+
node.children.push(this._build(items, j, right3, height - 1));
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
calcBBox(node, this.toBBox);
|
268
|
+
|
269
|
+
return node;
|
270
|
+
},
|
271
|
+
|
272
|
+
_chooseSubtree: function (bbox, node, level, path) {
|
273
|
+
|
274
|
+
var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
|
275
|
+
|
276
|
+
while (true) {
|
277
|
+
path.push(node);
|
278
|
+
|
279
|
+
if (node.leaf || path.length - 1 === level) break;
|
280
|
+
|
281
|
+
minArea = minEnlargement = Infinity;
|
282
|
+
|
283
|
+
for (i = 0, len = node.children.length; i < len; i++) {
|
284
|
+
child = node.children[i];
|
285
|
+
area = bboxArea(child.bbox);
|
286
|
+
enlargement = enlargedArea(bbox, child.bbox) - area;
|
287
|
+
|
288
|
+
// choose entry with the least area enlargement
|
289
|
+
if (enlargement < minEnlargement) {
|
290
|
+
minEnlargement = enlargement;
|
291
|
+
minArea = area < minArea ? area : minArea;
|
292
|
+
targetNode = child;
|
293
|
+
|
294
|
+
} else if (enlargement === minEnlargement) {
|
295
|
+
// otherwise choose one with the smallest area
|
296
|
+
if (area < minArea) {
|
297
|
+
minArea = area;
|
298
|
+
targetNode = child;
|
299
|
+
}
|
300
|
+
}
|
301
|
+
}
|
302
|
+
|
303
|
+
node = targetNode;
|
304
|
+
}
|
305
|
+
|
306
|
+
return node;
|
307
|
+
},
|
308
|
+
|
309
|
+
_insert: function (item, level, isNode) {
|
310
|
+
|
311
|
+
var toBBox = this.toBBox,
|
312
|
+
bbox = isNode ? item.bbox : toBBox(item),
|
313
|
+
insertPath = [];
|
314
|
+
|
315
|
+
// find the best node for accommodating the item, saving all nodes along the path too
|
316
|
+
var node = this._chooseSubtree(bbox, this.data, level, insertPath);
|
317
|
+
|
318
|
+
// put the item into the node
|
319
|
+
node.children.push(item);
|
320
|
+
extend(node.bbox, bbox);
|
321
|
+
|
322
|
+
// split on node overflow; propagate upwards if necessary
|
323
|
+
while (level >= 0) {
|
324
|
+
if (insertPath[level].children.length > this._maxEntries) {
|
325
|
+
this._split(insertPath, level);
|
326
|
+
level--;
|
327
|
+
} else break;
|
328
|
+
}
|
329
|
+
|
330
|
+
// adjust bboxes along the insertion path
|
331
|
+
this._adjustParentBBoxes(bbox, insertPath, level);
|
332
|
+
},
|
333
|
+
|
334
|
+
// split overflowed node into two
|
335
|
+
_split: function (insertPath, level) {
|
336
|
+
|
337
|
+
var node = insertPath[level],
|
338
|
+
M = node.children.length,
|
339
|
+
m = this._minEntries;
|
340
|
+
|
341
|
+
this._chooseSplitAxis(node, m, M);
|
342
|
+
|
343
|
+
var splitIndex = this._chooseSplitIndex(node, m, M);
|
344
|
+
|
345
|
+
var newNode = {
|
346
|
+
children: node.children.splice(splitIndex, node.children.length - splitIndex),
|
347
|
+
height: node.height,
|
348
|
+
bbox: null,
|
349
|
+
leaf: false
|
350
|
+
};
|
351
|
+
|
352
|
+
if (node.leaf) newNode.leaf = true;
|
353
|
+
|
354
|
+
calcBBox(node, this.toBBox);
|
355
|
+
calcBBox(newNode, this.toBBox);
|
356
|
+
|
357
|
+
if (level) insertPath[level - 1].children.push(newNode);
|
358
|
+
else this._splitRoot(node, newNode);
|
359
|
+
},
|
360
|
+
|
361
|
+
_splitRoot: function (node, newNode) {
|
362
|
+
// split root node
|
363
|
+
this.data = {
|
364
|
+
children: [node, newNode],
|
365
|
+
height: node.height + 1,
|
366
|
+
bbox: null,
|
367
|
+
leaf: false
|
368
|
+
};
|
369
|
+
calcBBox(this.data, this.toBBox);
|
370
|
+
},
|
371
|
+
|
372
|
+
_chooseSplitIndex: function (node, m, M) {
|
373
|
+
|
374
|
+
var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
|
375
|
+
|
376
|
+
minOverlap = minArea = Infinity;
|
377
|
+
|
378
|
+
for (i = m; i <= M - m; i++) {
|
379
|
+
bbox1 = distBBox(node, 0, i, this.toBBox);
|
380
|
+
bbox2 = distBBox(node, i, M, this.toBBox);
|
381
|
+
|
382
|
+
overlap = intersectionArea(bbox1, bbox2);
|
383
|
+
area = bboxArea(bbox1) + bboxArea(bbox2);
|
384
|
+
|
385
|
+
// choose distribution with minimum overlap
|
386
|
+
if (overlap < minOverlap) {
|
387
|
+
minOverlap = overlap;
|
388
|
+
index = i;
|
389
|
+
|
390
|
+
minArea = area < minArea ? area : minArea;
|
391
|
+
|
392
|
+
} else if (overlap === minOverlap) {
|
393
|
+
// otherwise choose distribution with minimum area
|
394
|
+
if (area < minArea) {
|
395
|
+
minArea = area;
|
396
|
+
index = i;
|
397
|
+
}
|
398
|
+
}
|
399
|
+
}
|
400
|
+
|
401
|
+
return index;
|
402
|
+
},
|
403
|
+
|
404
|
+
// sorts node children by the best axis for split
|
405
|
+
_chooseSplitAxis: function (node, m, M) {
|
406
|
+
|
407
|
+
var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX,
|
408
|
+
compareMinY = node.leaf ? this.compareMinY : compareNodeMinY,
|
409
|
+
xMargin = this._allDistMargin(node, m, M, compareMinX),
|
410
|
+
yMargin = this._allDistMargin(node, m, M, compareMinY);
|
411
|
+
|
412
|
+
// if total distributions margin value is minimal for x, sort by minX,
|
413
|
+
// otherwise it's already sorted by minY
|
414
|
+
if (xMargin < yMargin) node.children.sort(compareMinX);
|
415
|
+
},
|
416
|
+
|
417
|
+
// total margin of all possible split distributions where each node is at least m full
|
418
|
+
_allDistMargin: function (node, m, M, compare) {
|
419
|
+
|
420
|
+
node.children.sort(compare);
|
421
|
+
|
422
|
+
var toBBox = this.toBBox,
|
423
|
+
leftBBox = distBBox(node, 0, m, toBBox),
|
424
|
+
rightBBox = distBBox(node, M - m, M, toBBox),
|
425
|
+
margin = bboxMargin(leftBBox) + bboxMargin(rightBBox),
|
426
|
+
i, child;
|
427
|
+
|
428
|
+
for (i = m; i < M - m; i++) {
|
429
|
+
child = node.children[i];
|
430
|
+
extend(leftBBox, node.leaf ? toBBox(child) : child.bbox);
|
431
|
+
margin += bboxMargin(leftBBox);
|
432
|
+
}
|
433
|
+
|
434
|
+
for (i = M - m - 1; i >= m; i--) {
|
435
|
+
child = node.children[i];
|
436
|
+
extend(rightBBox, node.leaf ? toBBox(child) : child.bbox);
|
437
|
+
margin += bboxMargin(rightBBox);
|
438
|
+
}
|
439
|
+
|
440
|
+
return margin;
|
441
|
+
},
|
442
|
+
|
443
|
+
_adjustParentBBoxes: function (bbox, path, level) {
|
444
|
+
// adjust bboxes along the given tree path
|
445
|
+
for (var i = level; i >= 0; i--) {
|
446
|
+
extend(path[i].bbox, bbox);
|
447
|
+
}
|
448
|
+
},
|
449
|
+
|
450
|
+
_condense: function (path) {
|
451
|
+
// go through the path, removing empty nodes and updating bboxes
|
452
|
+
for (var i = path.length - 1, siblings; i >= 0; i--) {
|
453
|
+
if (path[i].children.length === 0) {
|
454
|
+
if (i > 0) {
|
455
|
+
siblings = path[i - 1].children;
|
456
|
+
siblings.splice(siblings.indexOf(path[i]), 1);
|
457
|
+
|
458
|
+
} else this.clear();
|
459
|
+
|
460
|
+
} else calcBBox(path[i], this.toBBox);
|
461
|
+
}
|
462
|
+
},
|
463
|
+
|
464
|
+
_initFormat: function (format) {
|
465
|
+
// data format (minX, minY, maxX, maxY accessors)
|
466
|
+
|
467
|
+
// uses eval-type function compilation instead of just accepting a toBBox function
|
468
|
+
// because the algorithms are very sensitive to sorting functions performance,
|
469
|
+
// so they should be dead simple and without inner calls
|
470
|
+
|
471
|
+
// jshint evil: true
|
472
|
+
|
473
|
+
var compareArr = ['return a', ' - b', ';'];
|
474
|
+
|
475
|
+
this.compareMinX = new Function('a', 'b', compareArr.join(format[0]));
|
476
|
+
this.compareMinY = new Function('a', 'b', compareArr.join(format[1]));
|
477
|
+
|
478
|
+
this.toBBox = new Function('a', 'return [a' + format.join(', a') + '];');
|
479
|
+
}
|
480
|
+
};
|
481
|
+
|
482
|
+
|
483
|
+
// calculate node's bbox from bboxes of its children
|
484
|
+
function calcBBox(node, toBBox) {
|
485
|
+
node.bbox = distBBox(node, 0, node.children.length, toBBox);
|
486
|
+
}
|
487
|
+
|
488
|
+
// min bounding rectangle of node children from k to p-1
|
489
|
+
function distBBox(node, k, p, toBBox) {
|
490
|
+
var bbox = empty();
|
491
|
+
|
492
|
+
for (var i = k, child; i < p; i++) {
|
493
|
+
child = node.children[i];
|
494
|
+
extend(bbox, node.leaf ? toBBox(child) : child.bbox);
|
495
|
+
}
|
496
|
+
|
497
|
+
return bbox;
|
498
|
+
}
|
499
|
+
|
500
|
+
function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; }
|
501
|
+
|
502
|
+
function extend(a, b) {
|
503
|
+
a[0] = Math.min(a[0], b[0]);
|
504
|
+
a[1] = Math.min(a[1], b[1]);
|
505
|
+
a[2] = Math.max(a[2], b[2]);
|
506
|
+
a[3] = Math.max(a[3], b[3]);
|
507
|
+
return a;
|
508
|
+
}
|
509
|
+
|
510
|
+
function compareNodeMinX(a, b) { return a.bbox[0] - b.bbox[0]; }
|
511
|
+
function compareNodeMinY(a, b) { return a.bbox[1] - b.bbox[1]; }
|
512
|
+
|
513
|
+
function bboxArea(a) { return (a[2] - a[0]) * (a[3] - a[1]); }
|
514
|
+
function bboxMargin(a) { return (a[2] - a[0]) + (a[3] - a[1]); }
|
515
|
+
|
516
|
+
function enlargedArea(a, b) {
|
517
|
+
return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) *
|
518
|
+
(Math.max(b[3], a[3]) - Math.min(b[1], a[1]));
|
519
|
+
}
|
520
|
+
|
521
|
+
function intersectionArea(a, b) {
|
522
|
+
var minX = Math.max(a[0], b[0]),
|
523
|
+
minY = Math.max(a[1], b[1]),
|
524
|
+
maxX = Math.min(a[2], b[2]),
|
525
|
+
maxY = Math.min(a[3], b[3]);
|
526
|
+
|
527
|
+
return Math.max(0, maxX - minX) *
|
528
|
+
Math.max(0, maxY - minY);
|
529
|
+
}
|
530
|
+
|
531
|
+
function contains(a, b) {
|
532
|
+
return a[0] <= b[0] &&
|
533
|
+
a[1] <= b[1] &&
|
534
|
+
b[2] <= a[2] &&
|
535
|
+
b[3] <= a[3];
|
536
|
+
}
|
537
|
+
|
538
|
+
function intersects(a, b) {
|
539
|
+
return b[0] <= a[2] &&
|
540
|
+
b[1] <= a[3] &&
|
541
|
+
b[2] >= a[0] &&
|
542
|
+
b[3] >= a[1];
|
543
|
+
}
|
544
|
+
|
545
|
+
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
|
546
|
+
// combines selection algorithm with binary divide & conquer approach
|
547
|
+
|
548
|
+
function multiSelect(arr, left, right, n, compare) {
|
549
|
+
var stack = [left, right],
|
550
|
+
mid;
|
551
|
+
|
552
|
+
while (stack.length) {
|
553
|
+
right = stack.pop();
|
554
|
+
left = stack.pop();
|
555
|
+
|
556
|
+
if (right - left <= n) continue;
|
557
|
+
|
558
|
+
mid = left + Math.ceil((right - left) / n / 2) * n;
|
559
|
+
select(arr, left, right, mid, compare);
|
560
|
+
|
561
|
+
stack.push(left, mid, mid, right);
|
562
|
+
}
|
563
|
+
}
|
564
|
+
|
565
|
+
// Floyd-Rivest selection algorithm:
|
566
|
+
// sort an array between left and right (inclusive) so that the smallest k elements come first (unordered)
|
567
|
+
function select(arr, left, right, k, compare) {
|
568
|
+
var n, i, z, s, sd, newLeft, newRight, t, j;
|
569
|
+
|
570
|
+
while (right > left) {
|
571
|
+
if (right - left > 600) {
|
572
|
+
n = right - left + 1;
|
573
|
+
i = k - left + 1;
|
574
|
+
z = Math.log(n);
|
575
|
+
s = 0.5 * Math.exp(2 * z / 3);
|
576
|
+
sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (i - n / 2 < 0 ? -1 : 1);
|
577
|
+
newLeft = Math.max(left, Math.floor(k - i * s / n + sd));
|
578
|
+
newRight = Math.min(right, Math.floor(k + (n - i) * s / n + sd));
|
579
|
+
select(arr, newLeft, newRight, k, compare);
|
580
|
+
}
|
581
|
+
|
582
|
+
t = arr[k];
|
583
|
+
i = left;
|
584
|
+
j = right;
|
585
|
+
|
586
|
+
swap(arr, left, k);
|
587
|
+
if (compare(arr[right], t) > 0) swap(arr, left, right);
|
588
|
+
|
589
|
+
while (i < j) {
|
590
|
+
swap(arr, i, j);
|
591
|
+
i++;
|
592
|
+
j--;
|
593
|
+
while (compare(arr[i], t) < 0) i++;
|
594
|
+
while (compare(arr[j], t) > 0) j--;
|
595
|
+
}
|
596
|
+
|
597
|
+
if (compare(arr[left], t) === 0) swap(arr, left, j);
|
598
|
+
else {
|
599
|
+
j++;
|
600
|
+
swap(arr, j, right);
|
601
|
+
}
|
602
|
+
|
603
|
+
if (j <= k) left = j + 1;
|
604
|
+
if (k <= j) right = j - 1;
|
605
|
+
}
|
606
|
+
}
|
607
|
+
|
608
|
+
function swap(arr, i, j) {
|
609
|
+
var tmp = arr[i];
|
610
|
+
arr[i] = arr[j];
|
611
|
+
arr[j] = tmp;
|
612
|
+
}
|
613
|
+
|
614
|
+
|
615
|
+
// export as AMD/CommonJS module or global variable
|
616
|
+
if (typeof define === 'function' && define.amd) define('rbush', function () { return rbush; });
|
617
|
+
else if (typeof module !== 'undefined') module.exports = rbush;
|
618
|
+
else if (typeof self !== 'undefined') self.rbush = rbush;
|
619
|
+
else window.rbush = rbush;
|
620
|
+
|
621
|
+
})();
|