From 197b936d50bba8371a8c69ad76790b1682a3be43 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Fri, 1 Jul 2016 23:12:51 -0400 Subject: [PATCH] Upgrade rbush to 2.x --- js/lib/id/index.js | 410 +++++++++++++++++----------------- js/lib/id/services.js | 319 +++++++++++++------------- js/lib/id/svg.js | 362 +++++++++++++++--------------- modules/core/tree.js | 40 ++-- modules/geo/extent.js | 4 + modules/services/mapillary.js | 6 +- modules/services/nominatim.js | 6 +- modules/svg/labels.js | 59 +++-- 8 files changed, 619 insertions(+), 587 deletions(-) diff --git a/js/lib/id/index.js b/js/lib/id/index.js index 58449c771..957bb709f 100644 --- a/js/lib/id/index.js +++ b/js/lib/id/index.js @@ -61,6 +61,10 @@ return [this[0][0], this[0][1], this[1][0], this[1][1]]; }, + bbox: function() { + return { minX: this[0][0], minY: this[0][1], maxX: this[1][0], maxY: this[1][1] }; + }, + polygon: function() { return [ [this[0][0], this[0][1]], @@ -2168,16 +2172,78 @@ return module = { exports: {} }, fn(module, module.exports), module.exports; } - var rbush = createCommonjsModule(function (module) { - /* - (c) 2015, Vladimir Agafonkin - RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles. - https://github.com/mourner/rbush - */ - - (function () { + var index$1 = createCommonjsModule(function (module) { 'use strict'; + module.exports = partialSort; + + // Floyd-Rivest selection algorithm: + // Rearrange items so that all items in the [left, k] range are smaller than all items in (k, right]; + // The k-th element will have the (k - left + 1)th smallest value in [left, right] + + function partialSort(arr, k, left, right, compare) { + left = left || 0; + right = right || (arr.length - 1); + compare = compare || defaultCompare; + + while (right > left) { + if (right - left > 600) { + var n = right - left + 1; + var m = k - left + 1; + var z = Math.log(n); + var s = 0.5 * Math.exp(2 * z / 3); + var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + partialSort(arr, k, newLeft, newRight, compare); + } + + var t = arr[k]; + var i = left; + var j = right; + + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } + } + + function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; + } + }); + + var require$$0 = (index$1 && typeof index$1 === 'object' && 'default' in index$1 ? index$1['default'] : index$1); + + var index = createCommonjsModule(function (module) { + 'use strict'; + + module.exports = rbush; + + var quickselect = require$$0; + function rbush(maxEntries, format) { if (!(this instanceof rbush)) return new rbush(maxEntries, format); @@ -2204,7 +2270,7 @@ result = [], toBBox = this.toBBox; - if (!intersects(bbox, node.bbox)) return result; + if (!intersects(bbox, node)) return result; var nodesToSearch = [], i, len, child, childBBox; @@ -2213,7 +2279,7 @@ for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - childBBox = node.leaf ? toBBox(child) : child.bbox; + childBBox = node.leaf ? toBBox(child) : child; if (intersects(bbox, childBBox)) { if (node.leaf) result.push(child); @@ -2232,7 +2298,7 @@ var node = this.data, toBBox = this.toBBox; - if (!intersects(bbox, node.bbox)) return false; + if (!intersects(bbox, node)) return false; var nodesToSearch = [], i, len, child, childBBox; @@ -2241,7 +2307,7 @@ for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - childBBox = node.leaf ? toBBox(child) : child.bbox; + childBBox = node.leaf ? toBBox(child) : child; if (intersects(bbox, childBBox)) { if (node.leaf || contains(bbox, childBBox)) return true; @@ -2296,16 +2362,11 @@ }, clear: function () { - this.data = { - children: [], - height: 1, - bbox: empty(), - leaf: true - }; + this.data = createNode([]); return this; }, - remove: function (item) { + remove: function (item, equalsFn) { if (!item) return this; var node = this.data, @@ -2325,7 +2386,7 @@ } if (node.leaf) { // check current node - index = node.children.indexOf(item); + index = findItem(item, node.children, equalsFn); if (index !== -1) { // item found, remove the item and condense tree upwards @@ -2336,7 +2397,7 @@ } } - if (!goingUp && !node.leaf && contains(node.bbox, bbox)) { // go down + if (!goingUp && !node.leaf && contains(node, bbox)) { // go down path.push(node); indexes.push(i); i = 0; @@ -2356,8 +2417,8 @@ toBBox: function (item) { return item; }, - compareMinX: function (a, b) { return a[0] - b[0]; }, - compareMinY: function (a, b) { return a[1] - b[1]; }, + compareMinX: compareNodeMinX, + compareMinY: compareNodeMinY, toJSON: function () { return this.data; }, @@ -2385,12 +2446,7 @@ if (N <= M) { // reached leaf level; return leaf - node = { - children: items.slice(left, right + 1), - height: 1, - bbox: null, - leaf: true - }; + node = createNode(items.slice(left, right + 1)); calcBBox(node, this.toBBox); return node; } @@ -2403,12 +2459,9 @@ M = Math.ceil(N / Math.pow(M, height - 1)); } - node = { - children: [], - height: height, - bbox: null, - leaf: false - }; + node = createNode([]); + node.leaf = false; + node.height = height; // split the items into M mostly square tiles @@ -2451,8 +2504,8 @@ for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - area = bboxArea(child.bbox); - enlargement = enlargedArea(bbox, child.bbox) - area; + area = bboxArea(child); + enlargement = enlargedArea(bbox, child) - area; // choose entry with the least area enlargement if (enlargement < minEnlargement) { @@ -2478,7 +2531,7 @@ _insert: function (item, level, isNode) { var toBBox = this.toBBox, - bbox = isNode ? item.bbox : toBBox(item), + bbox = isNode ? item : toBBox(item), insertPath = []; // find the best node for accommodating the item, saving all nodes along the path too @@ -2486,7 +2539,7 @@ // put the item into the node node.children.push(item); - extend(node.bbox, bbox); + extend(node, bbox); // split on node overflow; propagate upwards if necessary while (level >= 0) { @@ -2511,14 +2564,9 @@ var splitIndex = this._chooseSplitIndex(node, m, M); - var newNode = { - children: node.children.splice(splitIndex, node.children.length - splitIndex), - height: node.height, - bbox: null, - leaf: false - }; - - if (node.leaf) newNode.leaf = true; + var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); + newNode.height = node.height; + newNode.leaf = node.leaf; calcBBox(node, this.toBBox); calcBBox(newNode, this.toBBox); @@ -2529,12 +2577,9 @@ _splitRoot: function (node, newNode) { // split root node - this.data = { - children: [node, newNode], - height: node.height + 1, - bbox: null, - leaf: false - }; + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; calcBBox(this.data, this.toBBox); }, @@ -2596,13 +2641,13 @@ for (i = m; i < M - m; i++) { child = node.children[i]; - extend(leftBBox, node.leaf ? toBBox(child) : child.bbox); + extend(leftBBox, node.leaf ? toBBox(child) : child); margin += bboxMargin(leftBBox); } for (i = M - m - 1; i >= m; i--) { child = node.children[i]; - extend(rightBBox, node.leaf ? toBBox(child) : child.bbox); + extend(rightBBox, node.leaf ? toBBox(child) : child); margin += bboxMargin(rightBBox); } @@ -2612,7 +2657,7 @@ _adjustParentBBoxes: function (bbox, path, level) { // adjust bboxes along the given tree path for (var i = level; i >= 0; i--) { - extend(path[i].bbox, bbox); + extend(path[i], bbox); } }, @@ -2642,71 +2687,97 @@ 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 [a' + format.join(', a') + '];'); + 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++) { + if (equalsFn(item, items[i])) return i; + } + return -1; + } // calculate node's bbox from bboxes of its children function calcBBox(node, toBBox) { - node.bbox = distBBox(node, 0, node.children.length, toBBox); + distBBox(node, 0, node.children.length, toBBox, node); } // min bounding rectangle of node children from k to p-1 - function distBBox(node, k, p, toBBox) { - var bbox = empty(); + function distBBox(node, k, p, toBBox, destNode) { + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; for (var i = k, child; i < p; i++) { child = node.children[i]; - extend(bbox, node.leaf ? toBBox(child) : child.bbox); + extend(destNode, node.leaf ? toBBox(child) : child); } - return bbox; + return destNode; } - function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; } - function extend(a, b) { - a[0] = Math.min(a[0], b[0]); - a[1] = Math.min(a[1], b[1]); - a[2] = Math.max(a[2], b[2]); - a[3] = Math.max(a[3], b[3]); + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); return a; } - function compareNodeMinX(a, b) { return a.bbox[0] - b.bbox[0]; } - function compareNodeMinY(a, b) { return a.bbox[1] - b.bbox[1]; } + function compareNodeMinX(a, b) { return a.minX - b.minX; } + function compareNodeMinY(a, b) { return a.minY - b.minY; } - function bboxArea(a) { return (a[2] - a[0]) * (a[3] - a[1]); } - function bboxMargin(a) { return (a[2] - a[0]) + (a[3] - a[1]); } + function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } + function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } function enlargedArea(a, b) { - return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) * - (Math.max(b[3], a[3]) - Math.min(b[1], a[1])); + return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); } function intersectionArea(a, b) { - var minX = Math.max(a[0], b[0]), - minY = Math.max(a[1], b[1]), - maxX = Math.min(a[2], b[2]), - maxY = Math.min(a[3], b[3]); + 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); return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); } function contains(a, b) { - return a[0] <= b[0] && - a[1] <= b[1] && - b[2] <= a[2] && - b[3] <= a[3]; + return a.minX <= b.minX && + a.minY <= b.minY && + b.maxX <= a.maxX && + b.maxY <= a.maxY; } function intersects(a, b) { - return b[0] <= a[2] && - b[1] <= a[3] && - b[2] >= a[0] && - b[3] >= a[1]; + return b.minX <= a.maxX && + b.minY <= a.maxY && + b.maxX >= a.minX && + b.maxY >= a.minY; + } + + function createNode(children) { + return { + children: children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity + }; } // sort an array so that items come in groups of n unsorted items, with groups sorted between each other; @@ -2723,88 +2794,30 @@ if (right - left <= n) continue; mid = left + Math.ceil((right - left) / n / 2) * n; - select(arr, left, right, mid, compare); + quickselect(arr, mid, left, right, compare); stack.push(left, mid, mid, right); } } - - // Floyd-Rivest selection algorithm: - // sort an array between left and right (inclusive) so that the smallest k elements come first (unordered) - function select(arr, left, right, k, compare) { - var n, i, z, s, sd, newLeft, newRight, t, j; - - while (right > left) { - if (right - left > 600) { - n = right - left + 1; - i = k - left + 1; - z = Math.log(n); - s = 0.5 * Math.exp(2 * z / 3); - sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (i - n / 2 < 0 ? -1 : 1); - newLeft = Math.max(left, Math.floor(k - i * s / n + sd)); - newRight = Math.min(right, Math.floor(k + (n - i) * s / n + sd)); - select(arr, newLeft, newRight, k, compare); - } - - t = arr[k]; - i = left; - j = right; - - swap(arr, left, k); - if (compare(arr[right], t) > 0) swap(arr, left, right); - - while (i < j) { - swap(arr, i, j); - i++; - j--; - while (compare(arr[i], t) < 0) i++; - while (compare(arr[j], t) > 0) j--; - } - - if (compare(arr[left], t) === 0) swap(arr, left, j); - else { - j++; - swap(arr, j, right); - } - - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; - } - } - - function swap(arr, i, j) { - var tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; - } - - - // export as AMD/CommonJS module or global variable - if (typeof define === 'function' && define.amd) define('rbush', function () { return rbush; }); - else if (typeof module !== 'undefined') module.exports = rbush; - else if (typeof self !== 'undefined') self.rbush = rbush; - else window.rbush = rbush; - - })(); }); - var rbush$1 = (rbush && typeof rbush === 'object' && 'default' in rbush ? rbush['default'] : rbush); + var rbush = (index && typeof index === 'object' && 'default' in index ? index['default'] : index); function Tree(head) { - var rtree = rbush$1(), - rectangles = {}; + var rtree = rbush(), + bboxes = {}; - function entityRectangle(entity) { - var rect = entity.extent(head).rectangle(); - rect.id = entity.id; - rectangles[entity.id] = rect; - return rect; + function entityBBox(entity) { + var bbox = entity.extent(head).bbox(); + bbox.id = entity.id; + bboxes[entity.id] = bbox; + return bbox; } function updateParents(entity, insertions, memo) { head.parentWays(entity).forEach(function(way) { - if (rectangles[way.id]) { - rtree.remove(rectangles[way.id]); + if (bboxes[way.id]) { + rtree.remove(bboxes[way.id]); insertions[way.id] = way; } updateParents(way, insertions, memo); @@ -2813,8 +2826,8 @@ head.parentRelations(entity).forEach(function(relation) { if (memo[entity.id]) return; memo[entity.id] = true; - if (rectangles[relation.id]) { - rtree.remove(rectangles[relation.id]); + if (bboxes[relation.id]) { + rtree.remove(bboxes[relation.id]); insertions[relation.id] = relation; } updateParents(relation, insertions, memo); @@ -2832,11 +2845,11 @@ if (!entity.visible) continue; - if (head.entities.hasOwnProperty(entity.id) || rectangles[entity.id]) { + if (head.entities.hasOwnProperty(entity.id) || bboxes[entity.id]) { if (!force) { continue; - } else if (rectangles[entity.id]) { - rtree.remove(rectangles[entity.id]); + } else if (bboxes[entity.id]) { + rtree.remove(bboxes[entity.id]); } } @@ -2844,7 +2857,7 @@ updateParents(entity, insertions, {}); } - rtree.load(_.map(insertions, entityRectangle)); + rtree.load(_.map(insertions, entityBBox)); return tree; }; @@ -2857,12 +2870,12 @@ head = graph; diff.deleted().forEach(function(entity) { - rtree.remove(rectangles[entity.id]); - delete rectangles[entity.id]; + rtree.remove(bboxes[entity.id]); + delete bboxes[entity.id]; }); diff.modified().forEach(function(entity) { - rtree.remove(rectangles[entity.id]); + rtree.remove(bboxes[entity.id]); insertions[entity.id] = entity; updateParents(entity, insertions, {}); }); @@ -2871,11 +2884,11 @@ insertions[entity.id] = entity; }); - rtree.load(_.map(insertions, entityRectangle)); + rtree.load(_.map(insertions, entityBBox)); } - return rtree.search(extent.rectangle()).map(function(rect) { - return head.entity(rect.id); + return rtree.search(extent.bbox()).map(function(bbox) { + return head.entity(bbox.id); }); }; @@ -3527,7 +3540,7 @@ }; } - var index = createCommonjsModule(function (module) { + var index$2 = createCommonjsModule(function (module) { module.exports = element; module.exports.pair = pair; module.exports.format = format; @@ -12503,16 +12516,16 @@ var mouse = context.mouse(), pad = 50, - rect = [mouse[0] - pad, mouse[1] - pad, mouse[0] + pad, mouse[1] + pad], - ids = _.map(rtree.search(rect), 'id'); + bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad }, + ids = _.map(rtree.search(bbox), 'id'); if (!ids.length) return; layers.selectAll('.' + ids.join(', .')) .classed('proximate', true); } - var rtree = rbush$1(), - rectangles = {}; + var rtree = rbush(), + bboxes = {}; function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) { var hidePoints = !surface.selectAll('.node.point').node(); @@ -12522,10 +12535,10 @@ if (fullRedraw) { rtree.clear(); - rectangles = {}; + bboxes = {}; } else { for (i = 0; i < entities.length; i++) { - rtree.remove(rectangles[entities[i].id]); + rtree.remove(bboxes[entities[i].id]); } } @@ -12599,8 +12612,8 @@ y: coord[1] + offset[1], textAnchor: offset[2] }; - var rect = [p.x - m, p.y - m, p.x + width + m, p.y + height + m]; - if (tryInsert(rect, entity.id)) return p; + var bbox = { minX: p.x - m, minY: p.y - m, maxX: p.x + width + m, maxY: p.y + height + m }; + if (tryInsert(bbox, entity.id)) return p; } @@ -12616,14 +12629,14 @@ if (start < 0 || start + width > length) continue; var sub = subpath(nodes, start, start + width), rev = reverse(sub), - rect = [ - Math.min(sub[0][0], sub[sub.length - 1][0]) - 10, - Math.min(sub[0][1], sub[sub.length - 1][1]) - 10, - Math.max(sub[0][0], sub[sub.length - 1][0]) + 20, - Math.max(sub[0][1], sub[sub.length - 1][1]) + 30 - ]; + bbox = { + minX: Math.min(sub[0][0], sub[sub.length - 1][0]) - 10, + minY: Math.min(sub[0][1], sub[sub.length - 1][1]) - 10, + maxX: Math.max(sub[0][0], sub[sub.length - 1][0]) + 20, + maxY: Math.max(sub[0][1], sub[sub.length - 1][1]) + 30 + }; if (rev) sub = sub.reverse(); - if (tryInsert(rect, entity.id)) return { + if (tryInsert(bbox, entity.id)) return { 'font-size': height + 2, lineString: lineString(sub), startOffset: offset + '%' @@ -12635,7 +12648,7 @@ var centroid = path.centroid(entity.asGeoJSON(graph, true)), extent = entity.extent(graph), entitywidth = projection(extent[1])[0] - projection(extent[0])[0], - rect; + bbox; if (isNaN(centroid[0]) || entitywidth < 20) return; @@ -12652,24 +12665,23 @@ p.y = centroid[1] + textOffset; p.textAnchor = 'middle'; p.height = height; - rect = [p.x - width/2, p.y, p.x + width/2, p.y + height + textOffset]; + bbox = { minX: p.x - width/2, minY: p.y, maxX: p.x + width/2, maxY: p.y + height + textOffset }; } else { - rect = [iconX, iconY, iconX + iconSize, iconY + iconSize]; + bbox = { minX: iconX, minY: iconY, maxX: iconX + iconSize, maxY: iconY + iconSize }; } - if (tryInsert(rect, entity.id)) return p; + if (tryInsert(bbox, entity.id)) return p; } - function tryInsert(rect, id) { + function tryInsert(bbox, id) { // Check that label is visible - if (rect[0] < 0 || rect[1] < 0 || rect[2] > dimensions[0] || - rect[3] > dimensions[1]) return false; - var v = rtree.search(rect).length === 0; + if (bbox.minX < 0 || bbox.minY < 0 || bbox.maxX > dimensions[0] || bbox.maxY > dimensions[1]) return false; + var v = rtree.search(bbox).length === 0; if (v) { - rect.id = id; - rtree.insert(rect); - rectangles[id] = rect; + bbox.id = id; + rtree.insert(bbox); + bboxes[id] = bbox; } return v; } @@ -12706,11 +12718,11 @@ if (showDebug) { var gj = rtree.all().map(function(d) { return { type: 'Polygon', coordinates: [[ - [d[0], d[1]], - [d[2], d[1]], - [d[2], d[3]], - [d[0], d[3]], - [d[0], d[1]] + [d.minX, d.minY], + [d.maxX, d.minY], + [d.maxX, d.maxY], + [d.minX, d.maxY], + [d.minX, d.minY] ]]}; }); diff --git a/js/lib/id/services.js b/js/lib/id/services.js index e82b4f933..394ea85ab 100644 --- a/js/lib/id/services.js +++ b/js/lib/id/services.js @@ -8,16 +8,78 @@ return module = { exports: {} }, fn(module, module.exports), module.exports; } - var rbush = createCommonjsModule(function (module) { - /* - (c) 2015, Vladimir Agafonkin - RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles. - https://github.com/mourner/rbush - */ - - (function () { + var index$1 = createCommonjsModule(function (module) { 'use strict'; + module.exports = partialSort; + + // Floyd-Rivest selection algorithm: + // Rearrange items so that all items in the [left, k] range are smaller than all items in (k, right]; + // The k-th element will have the (k - left + 1)th smallest value in [left, right] + + function partialSort(arr, k, left, right, compare) { + left = left || 0; + right = right || (arr.length - 1); + compare = compare || defaultCompare; + + while (right > left) { + if (right - left > 600) { + var n = right - left + 1; + var m = k - left + 1; + var z = Math.log(n); + var s = 0.5 * Math.exp(2 * z / 3); + var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + partialSort(arr, k, newLeft, newRight, compare); + } + + var t = arr[k]; + var i = left; + var j = right; + + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } + } + + function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; + } + }); + + var require$$0 = (index$1 && typeof index$1 === 'object' && 'default' in index$1 ? index$1['default'] : index$1); + + var index = createCommonjsModule(function (module) { + 'use strict'; + + module.exports = rbush; + + var quickselect = require$$0; + function rbush(maxEntries, format) { if (!(this instanceof rbush)) return new rbush(maxEntries, format); @@ -44,7 +106,7 @@ result = [], toBBox = this.toBBox; - if (!intersects(bbox, node.bbox)) return result; + if (!intersects(bbox, node)) return result; var nodesToSearch = [], i, len, child, childBBox; @@ -53,7 +115,7 @@ for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - childBBox = node.leaf ? toBBox(child) : child.bbox; + childBBox = node.leaf ? toBBox(child) : child; if (intersects(bbox, childBBox)) { if (node.leaf) result.push(child); @@ -72,7 +134,7 @@ var node = this.data, toBBox = this.toBBox; - if (!intersects(bbox, node.bbox)) return false; + if (!intersects(bbox, node)) return false; var nodesToSearch = [], i, len, child, childBBox; @@ -81,7 +143,7 @@ for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - childBBox = node.leaf ? toBBox(child) : child.bbox; + childBBox = node.leaf ? toBBox(child) : child; if (intersects(bbox, childBBox)) { if (node.leaf || contains(bbox, childBBox)) return true; @@ -136,16 +198,11 @@ }, clear: function () { - this.data = { - children: [], - height: 1, - bbox: empty(), - leaf: true - }; + this.data = createNode([]); return this; }, - remove: function (item) { + remove: function (item, equalsFn) { if (!item) return this; var node = this.data, @@ -165,7 +222,7 @@ } if (node.leaf) { // check current node - index = node.children.indexOf(item); + index = findItem(item, node.children, equalsFn); if (index !== -1) { // item found, remove the item and condense tree upwards @@ -176,7 +233,7 @@ } } - if (!goingUp && !node.leaf && contains(node.bbox, bbox)) { // go down + if (!goingUp && !node.leaf && contains(node, bbox)) { // go down path.push(node); indexes.push(i); i = 0; @@ -196,8 +253,8 @@ toBBox: function (item) { return item; }, - compareMinX: function (a, b) { return a[0] - b[0]; }, - compareMinY: function (a, b) { return a[1] - b[1]; }, + compareMinX: compareNodeMinX, + compareMinY: compareNodeMinY, toJSON: function () { return this.data; }, @@ -225,12 +282,7 @@ if (N <= M) { // reached leaf level; return leaf - node = { - children: items.slice(left, right + 1), - height: 1, - bbox: null, - leaf: true - }; + node = createNode(items.slice(left, right + 1)); calcBBox(node, this.toBBox); return node; } @@ -243,12 +295,9 @@ M = Math.ceil(N / Math.pow(M, height - 1)); } - node = { - children: [], - height: height, - bbox: null, - leaf: false - }; + node = createNode([]); + node.leaf = false; + node.height = height; // split the items into M mostly square tiles @@ -291,8 +340,8 @@ for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - area = bboxArea(child.bbox); - enlargement = enlargedArea(bbox, child.bbox) - area; + area = bboxArea(child); + enlargement = enlargedArea(bbox, child) - area; // choose entry with the least area enlargement if (enlargement < minEnlargement) { @@ -318,7 +367,7 @@ _insert: function (item, level, isNode) { var toBBox = this.toBBox, - bbox = isNode ? item.bbox : toBBox(item), + bbox = isNode ? item : toBBox(item), insertPath = []; // find the best node for accommodating the item, saving all nodes along the path too @@ -326,7 +375,7 @@ // put the item into the node node.children.push(item); - extend(node.bbox, bbox); + extend(node, bbox); // split on node overflow; propagate upwards if necessary while (level >= 0) { @@ -351,14 +400,9 @@ var splitIndex = this._chooseSplitIndex(node, m, M); - var newNode = { - children: node.children.splice(splitIndex, node.children.length - splitIndex), - height: node.height, - bbox: null, - leaf: false - }; - - if (node.leaf) newNode.leaf = true; + var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); + newNode.height = node.height; + newNode.leaf = node.leaf; calcBBox(node, this.toBBox); calcBBox(newNode, this.toBBox); @@ -369,12 +413,9 @@ _splitRoot: function (node, newNode) { // split root node - this.data = { - children: [node, newNode], - height: node.height + 1, - bbox: null, - leaf: false - }; + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; calcBBox(this.data, this.toBBox); }, @@ -436,13 +477,13 @@ for (i = m; i < M - m; i++) { child = node.children[i]; - extend(leftBBox, node.leaf ? toBBox(child) : child.bbox); + extend(leftBBox, node.leaf ? toBBox(child) : child); margin += bboxMargin(leftBBox); } for (i = M - m - 1; i >= m; i--) { child = node.children[i]; - extend(rightBBox, node.leaf ? toBBox(child) : child.bbox); + extend(rightBBox, node.leaf ? toBBox(child) : child); margin += bboxMargin(rightBBox); } @@ -452,7 +493,7 @@ _adjustParentBBoxes: function (bbox, path, level) { // adjust bboxes along the given tree path for (var i = level; i >= 0; i--) { - extend(path[i].bbox, bbox); + extend(path[i], bbox); } }, @@ -482,71 +523,97 @@ 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 [a' + format.join(', a') + '];'); + 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++) { + if (equalsFn(item, items[i])) return i; + } + return -1; + } // calculate node's bbox from bboxes of its children function calcBBox(node, toBBox) { - node.bbox = distBBox(node, 0, node.children.length, toBBox); + distBBox(node, 0, node.children.length, toBBox, node); } // min bounding rectangle of node children from k to p-1 - function distBBox(node, k, p, toBBox) { - var bbox = empty(); + function distBBox(node, k, p, toBBox, destNode) { + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; for (var i = k, child; i < p; i++) { child = node.children[i]; - extend(bbox, node.leaf ? toBBox(child) : child.bbox); + extend(destNode, node.leaf ? toBBox(child) : child); } - return bbox; + return destNode; } - function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; } - function extend(a, b) { - a[0] = Math.min(a[0], b[0]); - a[1] = Math.min(a[1], b[1]); - a[2] = Math.max(a[2], b[2]); - a[3] = Math.max(a[3], b[3]); + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); return a; } - function compareNodeMinX(a, b) { return a.bbox[0] - b.bbox[0]; } - function compareNodeMinY(a, b) { return a.bbox[1] - b.bbox[1]; } + function compareNodeMinX(a, b) { return a.minX - b.minX; } + function compareNodeMinY(a, b) { return a.minY - b.minY; } - function bboxArea(a) { return (a[2] - a[0]) * (a[3] - a[1]); } - function bboxMargin(a) { return (a[2] - a[0]) + (a[3] - a[1]); } + function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } + function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } function enlargedArea(a, b) { - return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) * - (Math.max(b[3], a[3]) - Math.min(b[1], a[1])); + return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); } function intersectionArea(a, b) { - var minX = Math.max(a[0], b[0]), - minY = Math.max(a[1], b[1]), - maxX = Math.min(a[2], b[2]), - maxY = Math.min(a[3], b[3]); + 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); return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); } function contains(a, b) { - return a[0] <= b[0] && - a[1] <= b[1] && - b[2] <= a[2] && - b[3] <= a[3]; + return a.minX <= b.minX && + a.minY <= b.minY && + b.maxX <= a.maxX && + b.maxY <= a.maxY; } function intersects(a, b) { - return b[0] <= a[2] && - b[1] <= a[3] && - b[2] >= a[0] && - b[3] >= a[1]; + return b.minX <= a.maxX && + b.minY <= a.maxY && + b.maxX >= a.minX && + b.maxY >= a.minY; + } + + function createNode(children) { + return { + children: children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity + }; } // sort an array so that items come in groups of n unsorted items, with groups sorted between each other; @@ -563,72 +630,14 @@ if (right - left <= n) continue; mid = left + Math.ceil((right - left) / n / 2) * n; - select(arr, left, right, mid, compare); + quickselect(arr, mid, left, right, compare); stack.push(left, mid, mid, right); } } - - // Floyd-Rivest selection algorithm: - // sort an array between left and right (inclusive) so that the smallest k elements come first (unordered) - function select(arr, left, right, k, compare) { - var n, i, z, s, sd, newLeft, newRight, t, j; - - while (right > left) { - if (right - left > 600) { - n = right - left + 1; - i = k - left + 1; - z = Math.log(n); - s = 0.5 * Math.exp(2 * z / 3); - sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (i - n / 2 < 0 ? -1 : 1); - newLeft = Math.max(left, Math.floor(k - i * s / n + sd)); - newRight = Math.min(right, Math.floor(k + (n - i) * s / n + sd)); - select(arr, newLeft, newRight, k, compare); - } - - t = arr[k]; - i = left; - j = right; - - swap(arr, left, k); - if (compare(arr[right], t) > 0) swap(arr, left, right); - - while (i < j) { - swap(arr, i, j); - i++; - j--; - while (compare(arr[i], t) < 0) i++; - while (compare(arr[j], t) > 0) j--; - } - - if (compare(arr[left], t) === 0) swap(arr, left, j); - else { - j++; - swap(arr, j, right); - } - - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; - } - } - - function swap(arr, i, j) { - var tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; - } - - - // export as AMD/CommonJS module or global variable - if (typeof define === 'function' && define.amd) define('rbush', function () { return rbush; }); - else if (typeof module !== 'undefined') module.exports = rbush; - else if (typeof self !== 'undefined') self.rbush = rbush; - else window.rbush = rbush; - - })(); }); - var rbush$1 = (rbush && typeof rbush === 'object' && 'default' in rbush ? rbush['default'] : rbush); + var rbush = (index && typeof index === 'object' && 'default' in index ? index['default'] : index); function mapillary() { var mapillary = {}, @@ -828,7 +837,7 @@ if (which === 'images') d.ca = feature.properties.ca; if (which === 'signs') d.signs = feature.properties.rects; - features.push([loc[0], loc[1], loc[0], loc[1], d]); + features.push({minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d}); } cache.rtree.load(features); @@ -885,9 +894,9 @@ var partitions = partitionViewport(psize, projection, dimensions); return _.flatten(_.compact(_.map(partitions, function(extent) { - return rtree.search(extent.rectangle()) + return rtree.search(extent.bbox()) .slice(0, limit) - .map(function(d) { return d[4]; }); + .map(function(d) { return d.data; }); }))); } @@ -1009,8 +1018,8 @@ } iD.services.mapillary.cache = { - images: { inflight: {}, loaded: {}, rtree: rbush$1() }, - signs: { inflight: {}, loaded: {}, rtree: rbush$1() } + images: { inflight: {}, loaded: {}, rtree: rbush() }, + signs: { inflight: {}, loaded: {}, rtree: rbush() } }; iD.services.mapillary.image = null; @@ -1034,10 +1043,10 @@ nominatim.countryCode = function(location, callback) { var cache = iD.services.nominatim.cache, - countryCodes = cache.search([location[0], location[1], location[0], location[1]]); + countryCodes = cache.search({ minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] }); if (countryCodes.length > 0) - return callback(null, countryCodes[0][4]); + return callback(null, countryCodes[0].data); d3.json(endpoint + iD.util.qsString({ @@ -1053,14 +1062,14 @@ var extent = iD.geo.Extent(location).padByMeters(1000); - cache.insert(extent.rectangle().concat(result.address.country_code)); + cache.insert(Object.assign(extent.bbox(), { data: result.address.country_code })); callback(null, result.address.country_code); }); }; nominatim.reset = function() { - iD.services.nominatim.cache = rbush$1(); + iD.services.nominatim.cache = rbush(); return this; }; diff --git a/js/lib/id/svg.js b/js/lib/id/svg.js index e08ca650b..82ce66567 100644 --- a/js/lib/id/svg.js +++ b/js/lib/id/svg.js @@ -569,16 +569,78 @@ return module = { exports: {} }, fn(module, module.exports), module.exports; } - var rbush = createCommonjsModule(function (module) { - /* - (c) 2015, Vladimir Agafonkin - RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles. - https://github.com/mourner/rbush - */ - - (function () { + var index$1 = createCommonjsModule(function (module) { 'use strict'; + module.exports = partialSort; + + // Floyd-Rivest selection algorithm: + // Rearrange items so that all items in the [left, k] range are smaller than all items in (k, right]; + // The k-th element will have the (k - left + 1)th smallest value in [left, right] + + function partialSort(arr, k, left, right, compare) { + left = left || 0; + right = right || (arr.length - 1); + compare = compare || defaultCompare; + + while (right > left) { + if (right - left > 600) { + var n = right - left + 1; + var m = k - left + 1; + var z = Math.log(n); + var s = 0.5 * Math.exp(2 * z / 3); + var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + partialSort(arr, k, newLeft, newRight, compare); + } + + var t = arr[k]; + var i = left; + var j = right; + + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } + } + + function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; + } + }); + + var require$$0 = (index$1 && typeof index$1 === 'object' && 'default' in index$1 ? index$1['default'] : index$1); + + var index = createCommonjsModule(function (module) { + 'use strict'; + + module.exports = rbush; + + var quickselect = require$$0; + function rbush(maxEntries, format) { if (!(this instanceof rbush)) return new rbush(maxEntries, format); @@ -605,7 +667,7 @@ result = [], toBBox = this.toBBox; - if (!intersects(bbox, node.bbox)) return result; + if (!intersects(bbox, node)) return result; var nodesToSearch = [], i, len, child, childBBox; @@ -614,7 +676,7 @@ for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - childBBox = node.leaf ? toBBox(child) : child.bbox; + childBBox = node.leaf ? toBBox(child) : child; if (intersects(bbox, childBBox)) { if (node.leaf) result.push(child); @@ -633,7 +695,7 @@ var node = this.data, toBBox = this.toBBox; - if (!intersects(bbox, node.bbox)) return false; + if (!intersects(bbox, node)) return false; var nodesToSearch = [], i, len, child, childBBox; @@ -642,7 +704,7 @@ for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - childBBox = node.leaf ? toBBox(child) : child.bbox; + childBBox = node.leaf ? toBBox(child) : child; if (intersects(bbox, childBBox)) { if (node.leaf || contains(bbox, childBBox)) return true; @@ -697,16 +759,11 @@ }, clear: function () { - this.data = { - children: [], - height: 1, - bbox: empty(), - leaf: true - }; + this.data = createNode([]); return this; }, - remove: function (item) { + remove: function (item, equalsFn) { if (!item) return this; var node = this.data, @@ -726,7 +783,7 @@ } if (node.leaf) { // check current node - index = node.children.indexOf(item); + index = findItem(item, node.children, equalsFn); if (index !== -1) { // item found, remove the item and condense tree upwards @@ -737,7 +794,7 @@ } } - if (!goingUp && !node.leaf && contains(node.bbox, bbox)) { // go down + if (!goingUp && !node.leaf && contains(node, bbox)) { // go down path.push(node); indexes.push(i); i = 0; @@ -757,8 +814,8 @@ toBBox: function (item) { return item; }, - compareMinX: function (a, b) { return a[0] - b[0]; }, - compareMinY: function (a, b) { return a[1] - b[1]; }, + compareMinX: compareNodeMinX, + compareMinY: compareNodeMinY, toJSON: function () { return this.data; }, @@ -786,12 +843,7 @@ if (N <= M) { // reached leaf level; return leaf - node = { - children: items.slice(left, right + 1), - height: 1, - bbox: null, - leaf: true - }; + node = createNode(items.slice(left, right + 1)); calcBBox(node, this.toBBox); return node; } @@ -804,12 +856,9 @@ M = Math.ceil(N / Math.pow(M, height - 1)); } - node = { - children: [], - height: height, - bbox: null, - leaf: false - }; + node = createNode([]); + node.leaf = false; + node.height = height; // split the items into M mostly square tiles @@ -852,8 +901,8 @@ for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - area = bboxArea(child.bbox); - enlargement = enlargedArea(bbox, child.bbox) - area; + area = bboxArea(child); + enlargement = enlargedArea(bbox, child) - area; // choose entry with the least area enlargement if (enlargement < minEnlargement) { @@ -879,7 +928,7 @@ _insert: function (item, level, isNode) { var toBBox = this.toBBox, - bbox = isNode ? item.bbox : toBBox(item), + bbox = isNode ? item : toBBox(item), insertPath = []; // find the best node for accommodating the item, saving all nodes along the path too @@ -887,7 +936,7 @@ // put the item into the node node.children.push(item); - extend(node.bbox, bbox); + extend(node, bbox); // split on node overflow; propagate upwards if necessary while (level >= 0) { @@ -912,14 +961,9 @@ var splitIndex = this._chooseSplitIndex(node, m, M); - var newNode = { - children: node.children.splice(splitIndex, node.children.length - splitIndex), - height: node.height, - bbox: null, - leaf: false - }; - - if (node.leaf) newNode.leaf = true; + var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); + newNode.height = node.height; + newNode.leaf = node.leaf; calcBBox(node, this.toBBox); calcBBox(newNode, this.toBBox); @@ -930,12 +974,9 @@ _splitRoot: function (node, newNode) { // split root node - this.data = { - children: [node, newNode], - height: node.height + 1, - bbox: null, - leaf: false - }; + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; calcBBox(this.data, this.toBBox); }, @@ -997,13 +1038,13 @@ for (i = m; i < M - m; i++) { child = node.children[i]; - extend(leftBBox, node.leaf ? toBBox(child) : child.bbox); + extend(leftBBox, node.leaf ? toBBox(child) : child); margin += bboxMargin(leftBBox); } for (i = M - m - 1; i >= m; i--) { child = node.children[i]; - extend(rightBBox, node.leaf ? toBBox(child) : child.bbox); + extend(rightBBox, node.leaf ? toBBox(child) : child); margin += bboxMargin(rightBBox); } @@ -1013,7 +1054,7 @@ _adjustParentBBoxes: function (bbox, path, level) { // adjust bboxes along the given tree path for (var i = level; i >= 0; i--) { - extend(path[i].bbox, bbox); + extend(path[i], bbox); } }, @@ -1043,71 +1084,97 @@ 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 [a' + format.join(', a') + '];'); + 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++) { + if (equalsFn(item, items[i])) return i; + } + return -1; + } // calculate node's bbox from bboxes of its children function calcBBox(node, toBBox) { - node.bbox = distBBox(node, 0, node.children.length, toBBox); + distBBox(node, 0, node.children.length, toBBox, node); } // min bounding rectangle of node children from k to p-1 - function distBBox(node, k, p, toBBox) { - var bbox = empty(); + function distBBox(node, k, p, toBBox, destNode) { + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; for (var i = k, child; i < p; i++) { child = node.children[i]; - extend(bbox, node.leaf ? toBBox(child) : child.bbox); + extend(destNode, node.leaf ? toBBox(child) : child); } - return bbox; + return destNode; } - function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; } - function extend(a, b) { - a[0] = Math.min(a[0], b[0]); - a[1] = Math.min(a[1], b[1]); - a[2] = Math.max(a[2], b[2]); - a[3] = Math.max(a[3], b[3]); + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); return a; } - function compareNodeMinX(a, b) { return a.bbox[0] - b.bbox[0]; } - function compareNodeMinY(a, b) { return a.bbox[1] - b.bbox[1]; } + function compareNodeMinX(a, b) { return a.minX - b.minX; } + function compareNodeMinY(a, b) { return a.minY - b.minY; } - function bboxArea(a) { return (a[2] - a[0]) * (a[3] - a[1]); } - function bboxMargin(a) { return (a[2] - a[0]) + (a[3] - a[1]); } + function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } + function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } function enlargedArea(a, b) { - return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) * - (Math.max(b[3], a[3]) - Math.min(b[1], a[1])); + return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); } function intersectionArea(a, b) { - var minX = Math.max(a[0], b[0]), - minY = Math.max(a[1], b[1]), - maxX = Math.min(a[2], b[2]), - maxY = Math.min(a[3], b[3]); + 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); return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); } function contains(a, b) { - return a[0] <= b[0] && - a[1] <= b[1] && - b[2] <= a[2] && - b[3] <= a[3]; + return a.minX <= b.minX && + a.minY <= b.minY && + b.maxX <= a.maxX && + b.maxY <= a.maxY; } function intersects(a, b) { - return b[0] <= a[2] && - b[1] <= a[3] && - b[2] >= a[0] && - b[3] >= a[1]; + return b.minX <= a.maxX && + b.minY <= a.maxY && + b.maxX >= a.minX && + b.maxY >= a.minY; + } + + function createNode(children) { + return { + children: children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity + }; } // sort an array so that items come in groups of n unsorted items, with groups sorted between each other; @@ -1124,72 +1191,14 @@ if (right - left <= n) continue; mid = left + Math.ceil((right - left) / n / 2) * n; - select(arr, left, right, mid, compare); + quickselect(arr, mid, left, right, compare); stack.push(left, mid, mid, right); } } - - // Floyd-Rivest selection algorithm: - // sort an array between left and right (inclusive) so that the smallest k elements come first (unordered) - function select(arr, left, right, k, compare) { - var n, i, z, s, sd, newLeft, newRight, t, j; - - while (right > left) { - if (right - left > 600) { - n = right - left + 1; - i = k - left + 1; - z = Math.log(n); - s = 0.5 * Math.exp(2 * z / 3); - sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (i - n / 2 < 0 ? -1 : 1); - newLeft = Math.max(left, Math.floor(k - i * s / n + sd)); - newRight = Math.min(right, Math.floor(k + (n - i) * s / n + sd)); - select(arr, newLeft, newRight, k, compare); - } - - t = arr[k]; - i = left; - j = right; - - swap(arr, left, k); - if (compare(arr[right], t) > 0) swap(arr, left, right); - - while (i < j) { - swap(arr, i, j); - i++; - j--; - while (compare(arr[i], t) < 0) i++; - while (compare(arr[j], t) > 0) j--; - } - - if (compare(arr[left], t) === 0) swap(arr, left, j); - else { - j++; - swap(arr, j, right); - } - - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; - } - } - - function swap(arr, i, j) { - var tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; - } - - - // export as AMD/CommonJS module or global variable - if (typeof define === 'function' && define.amd) define('rbush', function () { return rbush; }); - else if (typeof module !== 'undefined') module.exports = rbush; - else if (typeof self !== 'undefined') self.rbush = rbush; - else window.rbush = rbush; - - })(); }); - var rbush$1 = (rbush && typeof rbush === 'object' && 'default' in rbush ? rbush['default'] : rbush); + var rbush = (index && typeof index === 'object' && 'default' in index ? index['default'] : index); function Labels(projection, context) { var path = d3.geo.path().projection(projection); @@ -1429,16 +1438,16 @@ var mouse = context.mouse(), pad = 50, - rect = [mouse[0] - pad, mouse[1] - pad, mouse[0] + pad, mouse[1] + pad], - ids = _.map(rtree.search(rect), 'id'); + bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad }, + ids = _.map(rtree.search(bbox), 'id'); if (!ids.length) return; layers.selectAll('.' + ids.join(', .')) .classed('proximate', true); } - var rtree = rbush$1(), - rectangles = {}; + var rtree = rbush(), + bboxes = {}; function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) { var hidePoints = !surface.selectAll('.node.point').node(); @@ -1448,10 +1457,10 @@ if (fullRedraw) { rtree.clear(); - rectangles = {}; + bboxes = {}; } else { for (i = 0; i < entities.length; i++) { - rtree.remove(rectangles[entities[i].id]); + rtree.remove(bboxes[entities[i].id]); } } @@ -1525,8 +1534,8 @@ y: coord[1] + offset[1], textAnchor: offset[2] }; - var rect = [p.x - m, p.y - m, p.x + width + m, p.y + height + m]; - if (tryInsert(rect, entity.id)) return p; + var bbox = { minX: p.x - m, minY: p.y - m, maxX: p.x + width + m, maxY: p.y + height + m }; + if (tryInsert(bbox, entity.id)) return p; } @@ -1542,14 +1551,14 @@ if (start < 0 || start + width > length) continue; var sub = subpath(nodes, start, start + width), rev = reverse(sub), - rect = [ - Math.min(sub[0][0], sub[sub.length - 1][0]) - 10, - Math.min(sub[0][1], sub[sub.length - 1][1]) - 10, - Math.max(sub[0][0], sub[sub.length - 1][0]) + 20, - Math.max(sub[0][1], sub[sub.length - 1][1]) + 30 - ]; + bbox = { + minX: Math.min(sub[0][0], sub[sub.length - 1][0]) - 10, + minY: Math.min(sub[0][1], sub[sub.length - 1][1]) - 10, + maxX: Math.max(sub[0][0], sub[sub.length - 1][0]) + 20, + maxY: Math.max(sub[0][1], sub[sub.length - 1][1]) + 30 + }; if (rev) sub = sub.reverse(); - if (tryInsert(rect, entity.id)) return { + if (tryInsert(bbox, entity.id)) return { 'font-size': height + 2, lineString: lineString(sub), startOffset: offset + '%' @@ -1561,7 +1570,7 @@ var centroid = path.centroid(entity.asGeoJSON(graph, true)), extent = entity.extent(graph), entitywidth = projection(extent[1])[0] - projection(extent[0])[0], - rect; + bbox; if (isNaN(centroid[0]) || entitywidth < 20) return; @@ -1578,24 +1587,23 @@ p.y = centroid[1] + textOffset; p.textAnchor = 'middle'; p.height = height; - rect = [p.x - width/2, p.y, p.x + width/2, p.y + height + textOffset]; + bbox = { minX: p.x - width/2, minY: p.y, maxX: p.x + width/2, maxY: p.y + height + textOffset }; } else { - rect = [iconX, iconY, iconX + iconSize, iconY + iconSize]; + bbox = { minX: iconX, minY: iconY, maxX: iconX + iconSize, maxY: iconY + iconSize }; } - if (tryInsert(rect, entity.id)) return p; + if (tryInsert(bbox, entity.id)) return p; } - function tryInsert(rect, id) { + function tryInsert(bbox, id) { // Check that label is visible - if (rect[0] < 0 || rect[1] < 0 || rect[2] > dimensions[0] || - rect[3] > dimensions[1]) return false; - var v = rtree.search(rect).length === 0; + if (bbox.minX < 0 || bbox.minY < 0 || bbox.maxX > dimensions[0] || bbox.maxY > dimensions[1]) return false; + var v = rtree.search(bbox).length === 0; if (v) { - rect.id = id; - rtree.insert(rect); - rectangles[id] = rect; + bbox.id = id; + rtree.insert(bbox); + bboxes[id] = bbox; } return v; } @@ -1632,11 +1640,11 @@ if (showDebug) { var gj = rtree.all().map(function(d) { return { type: 'Polygon', coordinates: [[ - [d[0], d[1]], - [d[2], d[1]], - [d[2], d[3]], - [d[0], d[3]], - [d[0], d[1]] + [d.minX, d.minY], + [d.maxX, d.minY], + [d.maxX, d.maxY], + [d.minX, d.maxY], + [d.minX, d.minY] ]]}; }); diff --git a/modules/core/tree.js b/modules/core/tree.js index 538c55f3e..829decb88 100644 --- a/modules/core/tree.js +++ b/modules/core/tree.js @@ -3,19 +3,19 @@ import rbush from 'rbush'; export function Tree(head) { var rtree = rbush(), - rectangles = {}; + bboxes = {}; - function entityRectangle(entity) { - var rect = entity.extent(head).rectangle(); - rect.id = entity.id; - rectangles[entity.id] = rect; - return rect; + function entityBBox(entity) { + var bbox = entity.extent(head).bbox(); + bbox.id = entity.id; + bboxes[entity.id] = bbox; + return bbox; } function updateParents(entity, insertions, memo) { head.parentWays(entity).forEach(function(way) { - if (rectangles[way.id]) { - rtree.remove(rectangles[way.id]); + if (bboxes[way.id]) { + rtree.remove(bboxes[way.id]); insertions[way.id] = way; } updateParents(way, insertions, memo); @@ -24,8 +24,8 @@ export function Tree(head) { head.parentRelations(entity).forEach(function(relation) { if (memo[entity.id]) return; memo[entity.id] = true; - if (rectangles[relation.id]) { - rtree.remove(rectangles[relation.id]); + if (bboxes[relation.id]) { + rtree.remove(bboxes[relation.id]); insertions[relation.id] = relation; } updateParents(relation, insertions, memo); @@ -43,11 +43,11 @@ export function Tree(head) { if (!entity.visible) continue; - if (head.entities.hasOwnProperty(entity.id) || rectangles[entity.id]) { + if (head.entities.hasOwnProperty(entity.id) || bboxes[entity.id]) { if (!force) { continue; - } else if (rectangles[entity.id]) { - rtree.remove(rectangles[entity.id]); + } else if (bboxes[entity.id]) { + rtree.remove(bboxes[entity.id]); } } @@ -55,7 +55,7 @@ export function Tree(head) { updateParents(entity, insertions, {}); } - rtree.load(_.map(insertions, entityRectangle)); + rtree.load(_.map(insertions, entityBBox)); return tree; }; @@ -68,12 +68,12 @@ export function Tree(head) { head = graph; diff.deleted().forEach(function(entity) { - rtree.remove(rectangles[entity.id]); - delete rectangles[entity.id]; + rtree.remove(bboxes[entity.id]); + delete bboxes[entity.id]; }); diff.modified().forEach(function(entity) { - rtree.remove(rectangles[entity.id]); + rtree.remove(bboxes[entity.id]); insertions[entity.id] = entity; updateParents(entity, insertions, {}); }); @@ -82,11 +82,11 @@ export function Tree(head) { insertions[entity.id] = entity; }); - rtree.load(_.map(insertions, entityRectangle)); + rtree.load(_.map(insertions, entityBBox)); } - return rtree.search(extent.rectangle()).map(function(rect) { - return head.entity(rect.id); + return rtree.search(extent.bbox()).map(function(bbox) { + return head.entity(bbox.id); }); }; diff --git a/modules/geo/extent.js b/modules/geo/extent.js index c4f9c2a47..841288765 100644 --- a/modules/geo/extent.js +++ b/modules/geo/extent.js @@ -50,6 +50,10 @@ _.extend(Extent.prototype, { return [this[0][0], this[0][1], this[1][0], this[1][1]]; }, + bbox: function() { + return { minX: this[0][0], minY: this[0][1], maxX: this[1][0], maxY: this[1][1] }; + }, + polygon: function() { return [ [this[0][0], this[0][1]], diff --git a/modules/services/mapillary.js b/modules/services/mapillary.js index e23cc25fd..09c2be8e4 100644 --- a/modules/services/mapillary.js +++ b/modules/services/mapillary.js @@ -198,7 +198,7 @@ export function mapillary() { if (which === 'images') d.ca = feature.properties.ca; if (which === 'signs') d.signs = feature.properties.rects; - features.push([loc[0], loc[1], loc[0], loc[1], d]); + features.push({minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d}); } cache.rtree.load(features); @@ -255,9 +255,9 @@ export function mapillary() { var partitions = partitionViewport(psize, projection, dimensions); return _.flatten(_.compact(_.map(partitions, function(extent) { - return rtree.search(extent.rectangle()) + return rtree.search(extent.bbox()) .slice(0, limit) - .map(function(d) { return d[4]; }); + .map(function(d) { return d.data; }); }))); } diff --git a/modules/services/nominatim.js b/modules/services/nominatim.js index 3cdddbe0a..a862f52ed 100644 --- a/modules/services/nominatim.js +++ b/modules/services/nominatim.js @@ -7,10 +7,10 @@ export function nominatim() { nominatim.countryCode = function(location, callback) { var cache = iD.services.nominatim.cache, - countryCodes = cache.search([location[0], location[1], location[0], location[1]]); + countryCodes = cache.search({ minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] }); if (countryCodes.length > 0) - return callback(null, countryCodes[0][4]); + return callback(null, countryCodes[0].data); d3.json(endpoint + iD.util.qsString({ @@ -26,7 +26,7 @@ export function nominatim() { var extent = iD.geo.Extent(location).padByMeters(1000); - cache.insert(extent.rectangle().concat(result.address.country_code)); + cache.insert(Object.assign(extent.bbox(), { data: result.address.country_code })); callback(null, result.address.country_code); }); diff --git a/modules/svg/labels.js b/modules/svg/labels.js index 0b5145a75..62230fa27 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -238,8 +238,8 @@ export function Labels(projection, context) { var mouse = context.mouse(), pad = 50, - rect = [mouse[0] - pad, mouse[1] - pad, mouse[0] + pad, mouse[1] + pad], - ids = _.map(rtree.search(rect), 'id'); + bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad }, + ids = _.map(rtree.search(bbox), 'id'); if (!ids.length) return; layers.selectAll('.' + ids.join(', .')) @@ -247,7 +247,7 @@ export function Labels(projection, context) { } var rtree = rbush(), - rectangles = {}; + bboxes = {}; function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) { var hidePoints = !surface.selectAll('.node.point').node(); @@ -257,10 +257,10 @@ export function Labels(projection, context) { if (fullRedraw) { rtree.clear(); - rectangles = {}; + bboxes = {}; } else { for (i = 0; i < entities.length; i++) { - rtree.remove(rectangles[entities[i].id]); + rtree.remove(bboxes[entities[i].id]); } } @@ -334,8 +334,8 @@ export function Labels(projection, context) { y: coord[1] + offset[1], textAnchor: offset[2] }; - var rect = [p.x - m, p.y - m, p.x + width + m, p.y + height + m]; - if (tryInsert(rect, entity.id)) return p; + var bbox = { minX: p.x - m, minY: p.y - m, maxX: p.x + width + m, maxY: p.y + height + m }; + if (tryInsert(bbox, entity.id)) return p; } @@ -351,14 +351,14 @@ export function Labels(projection, context) { if (start < 0 || start + width > length) continue; var sub = subpath(nodes, start, start + width), rev = reverse(sub), - rect = [ - Math.min(sub[0][0], sub[sub.length - 1][0]) - 10, - Math.min(sub[0][1], sub[sub.length - 1][1]) - 10, - Math.max(sub[0][0], sub[sub.length - 1][0]) + 20, - Math.max(sub[0][1], sub[sub.length - 1][1]) + 30 - ]; + bbox = { + minX: Math.min(sub[0][0], sub[sub.length - 1][0]) - 10, + minY: Math.min(sub[0][1], sub[sub.length - 1][1]) - 10, + maxX: Math.max(sub[0][0], sub[sub.length - 1][0]) + 20, + maxY: Math.max(sub[0][1], sub[sub.length - 1][1]) + 30 + }; if (rev) sub = sub.reverse(); - if (tryInsert(rect, entity.id)) return { + if (tryInsert(bbox, entity.id)) return { 'font-size': height + 2, lineString: lineString(sub), startOffset: offset + '%' @@ -370,7 +370,7 @@ export function Labels(projection, context) { var centroid = path.centroid(entity.asGeoJSON(graph, true)), extent = entity.extent(graph), entitywidth = projection(extent[1])[0] - projection(extent[0])[0], - rect; + bbox; if (isNaN(centroid[0]) || entitywidth < 20) return; @@ -387,24 +387,23 @@ export function Labels(projection, context) { p.y = centroid[1] + textOffset; p.textAnchor = 'middle'; p.height = height; - rect = [p.x - width/2, p.y, p.x + width/2, p.y + height + textOffset]; + bbox = { minX: p.x - width/2, minY: p.y, maxX: p.x + width/2, maxY: p.y + height + textOffset }; } else { - rect = [iconX, iconY, iconX + iconSize, iconY + iconSize]; + bbox = { minX: iconX, minY: iconY, maxX: iconX + iconSize, maxY: iconY + iconSize }; } - if (tryInsert(rect, entity.id)) return p; + if (tryInsert(bbox, entity.id)) return p; } - function tryInsert(rect, id) { + function tryInsert(bbox, id) { // Check that label is visible - if (rect[0] < 0 || rect[1] < 0 || rect[2] > dimensions[0] || - rect[3] > dimensions[1]) return false; - var v = rtree.search(rect).length === 0; + if (bbox.minX < 0 || bbox.minY < 0 || bbox.maxX > dimensions[0] || bbox.maxY > dimensions[1]) return false; + var v = rtree.search(bbox).length === 0; if (v) { - rect.id = id; - rtree.insert(rect); - rectangles[id] = rect; + bbox.id = id; + rtree.insert(bbox); + bboxes[id] = bbox; } return v; } @@ -441,11 +440,11 @@ export function Labels(projection, context) { if (showDebug) { var gj = rtree.all().map(function(d) { return { type: 'Polygon', coordinates: [[ - [d[0], d[1]], - [d[2], d[1]], - [d[2], d[3]], - [d[0], d[3]], - [d[0], d[1]] + [d.minX, d.minY], + [d.maxX, d.minY], + [d.maxX, d.maxY], + [d.minX, d.maxY], + [d.minX, d.minY] ]]}; });