diff --git a/index.html b/index.html
index e9598a0ec..3e20c81f7 100644
--- a/index.html
+++ b/index.html
@@ -151,6 +151,7 @@
+
diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js
index 728b8ad15..0d234d1c4 100644
--- a/js/id/behavior/lasso.js
+++ b/js/id/behavior/lasso.js
@@ -49,7 +49,7 @@ iD.behavior.Lasso = function(context) {
.on('mouseup.lasso', null);
if (d3.event.clientX !== pos[0] || d3.event.clientY !== pos[1]) {
- var selected = context.graph().intersects(extent).filter(function (entity) {
+ var selected = context.intersects(extent).filter(function (entity) {
return entity.type === 'node';
});
diff --git a/js/id/core/difference.js b/js/id/core/difference.js
index e8faef149..801d2f633 100644
--- a/js/id/core/difference.js
+++ b/js/id/core/difference.js
@@ -25,6 +25,18 @@ iD.Difference = function(base, head) {
}
});
+ function addParents(parents, result) {
+ for (var i = 0; i < parents.length; i++) {
+ var parent = parents[i];
+
+ if (parent.id in result)
+ continue;
+
+ result[parent.id] = parent;
+ addParents(head.parentRelations(parent), result);
+ }
+ }
+
var difference = {};
difference.length = function() {
@@ -67,21 +79,18 @@ iD.Difference = function(base, head) {
return result;
};
+ difference.addParents = function(entities) {
+
+ for (var i in entities) {
+ addParents(head.parentWays(entities[i]), entities);
+ addParents(head.parentRelations(entities[i]), entities);
+ }
+ return entities;
+ },
+
difference.complete = function(extent) {
var result = {}, id, change;
- function addParents(parents) {
- for (var i = 0; i < parents.length; i++) {
- var parent = parents[i];
-
- if (parent.id in result)
- continue;
-
- result[parent.id] = parent;
- addParents(head.parentRelations(parent));
- }
- }
-
for (id in changes) {
change = changes[id];
@@ -99,10 +108,10 @@ iD.Difference = function(base, head) {
if (entity.type === 'way') {
var nh = h ? h.nodes : [],
nb = b ? b.nodes : [],
- diff;
+ diff, i;
diff = _.difference(nh, nb);
- for (var i = 0; i < diff.length; i++) {
+ for (i = 0; i < diff.length; i++) {
result[diff[i]] = head.entity(diff[i]);
}
@@ -111,9 +120,9 @@ iD.Difference = function(base, head) {
result[diff[i]] = head.entity(diff[i]);
}
}
+ addParents(head.parentWays(entity), result);
+ addParents(head.parentRelations(entity), result);
- addParents(head.parentWays(entity));
- addParents(head.parentRelations(entity));
}
return result;
diff --git a/js/id/core/graph.js b/js/id/core/graph.js
index a595f0df6..e96ef49d6 100644
--- a/js/id/core/graph.js
+++ b/js/id/core/graph.js
@@ -226,21 +226,10 @@ iD.Graph.prototype = {
return this;
},
- // get all objects that intersect an extent.
- intersects: function(extent) {
- var items = [];
- for (var i in this.entities) {
- var entity = this.entities[i];
- if (entity && this.hasAllChildren(entity) && entity.intersects(extent, this)) {
- items.push(entity);
- }
- }
- return items;
- },
-
hasAllChildren: function(entity) {
// we're only checking changed entities, since we assume fetched data
// must have all children present
+ var i;
if (this.entities.hasOwnProperty(entity.id)) {
if (entity.type === 'way') {
for (i = 0; i < entity.nodes.length; i++) {
diff --git a/js/id/core/history.js b/js/id/core/history.js
index bfd0d5a8c..3ae02b42d 100644
--- a/js/id/core/history.js
+++ b/js/id/core/history.js
@@ -1,5 +1,5 @@
iD.History = function(context) {
- var stack, index,
+ var stack, index, tree,
imagery_used = 'Bing',
dispatch = d3.dispatch('change', 'undone', 'redone'),
lock = false;
@@ -41,10 +41,19 @@ iD.History = function(context) {
},
merge: function(entities) {
+
+
+ var base = stack[0].graph.base(),
+ newentities = Object.keys(entities).filter(function(i) {
+ return !base.entities[i];
+ });
+
for (var i = 0; i < stack.length; i++) {
stack[i].graph.rebase(entities);
}
+ tree.rebase(newentities);
+
dispatch.change();
},
@@ -124,6 +133,10 @@ iD.History = function(context) {
}
},
+ intersects: function(extent) {
+ return tree.intersects(extent, stack[index].graph);
+ },
+
difference: function() {
var base = stack[0].graph,
head = stack[index].graph;
@@ -168,6 +181,7 @@ iD.History = function(context) {
reset: function() {
stack = [{graph: iD.Graph()}];
index = 0;
+ tree = iD.Tree(stack[0].graph);
dispatch.change();
},
diff --git a/js/id/core/tree.js b/js/id/core/tree.js
new file mode 100644
index 000000000..94b28f12e
--- /dev/null
+++ b/js/id/core/tree.js
@@ -0,0 +1,92 @@
+
+
+iD.Tree = function(graph) {
+
+ var rtree = new RTree(),
+ m = 1000 * 1000 * 100,
+ head = graph,
+ queuedCreated = [],
+ queuedModified = [],
+ x, y, dx, dy, rebased;
+
+ function extentRectangle(extent) {
+ x = m * extent[0][0],
+ y = m * extent[0][1],
+ dx = m * extent[1][0] - x || 1,
+ dy = m * extent[1][1] - y || 1;
+ return new RTree.Rectangle(~~x, ~~y, ~~dx - 1, ~~dy - 1);
+ }
+
+ function insert(entity) {
+ rtree.insert(extentRectangle(entity.extent(head)), entity.id);
+ }
+
+ function remove(entity) {
+ rtree.remove(extentRectangle(entity.extent(graph)), entity.id);
+ }
+
+ function reinsert(entity) {
+ remove(graph.entities[entity.id]);
+ insert(entity);
+ }
+
+ var tree = {
+
+ rebase: function(entities) {
+ for (var i = 0; i < entities.length; i++) {
+ if (!graph.entities.hasOwnProperty(entities[i])) {
+ insert(graph.entity(entities[i]), true);
+ }
+ }
+ rebased = true;
+ return tree;
+ },
+
+ intersects: function(extent, g) {
+
+ head = g;
+
+ if (graph !== head || rebased) {
+ var diff = iD.Difference(graph, head),
+ modified = {};
+
+ diff.modified().forEach(function(d) {
+ var loc = graph.entities[d.id].loc;
+ if (!loc || loc[0] !== d.loc[0] || loc[1] !== d.loc[1]) {
+ modified[d.id] = d;
+ }
+ });
+
+ var created = diff.created().concat(queuedCreated);
+ modified = d3.values(diff.addParents(modified)).concat(queuedModified);
+ queuedCreated = [];
+ queuedModified = [];
+
+ modified.forEach(function(d) {
+ if (head.hasAllChildren(d)) reinsert(d);
+ else queuedModified.push(d);
+ });
+
+ created.forEach(function(d) {
+ if (head.hasAllChildren(d)) insert(d);
+ else queuedCreated.push(d);
+ });
+
+ diff.deleted().forEach(remove);
+
+ graph = head;
+ rebased = false;
+ }
+
+ return rtree.search(extentRectangle(extent))
+ .map(function(id) { return graph.entity(id); });
+ },
+
+ graph: function() {
+ return graph;
+ }
+
+ };
+
+ return tree;
+};
diff --git a/js/id/id.js b/js/id/id.js
index e2144c237..29338b1a3 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -36,6 +36,7 @@ window.iD = function () {
context.undo = history.undo;
context.redo = history.redo;
context.changes = history.changes;
+ context.intersects = history.intersects;
/* Graph */
context.entity = function(id) {
diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js
index c3d5ec696..d4c082fad 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -65,7 +65,7 @@ iD.Map = function(context) {
graph = context.graph();
if (!difference) {
- all = graph.intersects(extent);
+ all = context.intersects(extent);
filter = d3.functor(true);
} else {
var complete = difference.complete(extent);
diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js
index 2389f266a..cc44316c4 100644
--- a/js/id/ui/contributors.js
+++ b/js/id/ui/contributors.js
@@ -2,7 +2,7 @@ iD.ui.Contributors = function(context) {
function update(selection) {
var users = {},
limit = 3,
- entities = context.graph().intersects(context.map().extent());
+ entities = context.intersects(context.map().extent());
for (var i in entities) {
if (entities[i].user) users[entities[i].user] = true;
diff --git a/test/index.html b/test/index.html
index 4d569f1f1..6ef656a35 100644
--- a/test/index.html
+++ b/test/index.html
@@ -137,6 +137,7 @@
+
@@ -186,6 +187,7 @@
+
diff --git a/test/spec/core/tree.js b/test/spec/core/tree.js
new file mode 100644
index 000000000..8cd4e55b6
--- /dev/null
+++ b/test/spec/core/tree.js
@@ -0,0 +1,56 @@
+describe("iD.Tree", function() {
+ var tree;
+
+ beforeEach(function() {
+ tree = iD.Tree(iD.Graph());
+ });
+
+ describe("#rebase", function() {
+ it("adds entities to the tree", function() {
+ var node = iD.Node({ id: 'n', loc: [1, 1]});
+ tree.graph().rebase({ 'n': node });
+ tree.rebase(['n']);
+ expect(tree.intersects(iD.geo.Extent([0, 0], [2, 2]), tree.graph())).to.eql([node]);
+ });
+
+ it("does not insert if entity has a modified version", function() {
+ var node = iD.Node({ id: 'n', loc: [1, 1]}),
+ node_ = node.update({ loc: [10, 10]}),
+ g = tree.graph().replace(node_);
+ expect(tree.intersects(iD.geo.Extent([9, 9], [11, 11]), g)).to.eql([node_]);
+ tree.graph().rebase({ 'n': node });
+ tree.rebase(['n']);
+ expect(tree.intersects(iD.geo.Extent([0, 0], [2, 2]), g)).to.eql([]);
+ expect(tree.intersects(iD.geo.Extent([0, 0], [11, 11]), g)).to.eql([node_]);
+ });
+ });
+
+ describe("#intersects", function() {
+ it("excludes entities with missing children, adds them when all are present", function() {
+ var way = iD.Way({id: 'w1', nodes: ['n']});
+ var g = tree.graph().replace(way);
+ expect(tree.intersects(iD.geo.Extent([0, 0], [1, 1]), g)).to.eql([]);
+ var node = iD.Node({id: 'n', loc: [0.5, 0.5]});
+ g = tree.graph().replace(node);
+ expect(tree.intersects(iD.geo.Extent([0, 0], [1, 1]), g)).to.eql([way, node]);
+ });
+
+ it("includes entities that used to have missing children, after rebase added them", function() {
+ var base = tree.graph();
+ var way = iD.Way({id: 'w1', nodes: ['n']});
+ var g = base.replace(way);
+ expect(tree.intersects(iD.geo.Extent([0, 0], [1, 1]), g)).to.eql([]);
+ var node = iD.Node({id: 'n', loc: [0.5, 0.5]});
+ base.rebase({ 'n': node });
+ tree.rebase(['n']);
+ expect(tree.intersects(iD.geo.Extent([0, 0], [1, 1]), g)).to.eql([way, node]);
+ });
+
+ it("includes entities within extent, excludes those without", function() {
+ var n1 = iD.Node({ id: 'n1', loc: [1, 1]});
+ var n2 = iD.Node({ id: 'n2', loc: [3, 3]});
+ var g = tree.graph().replace(n1).replace(n2);
+ expect(tree.intersects(iD.geo.Extent([0, 0], [1.1, 1.1]), g)).to.eql([n1]);
+ });
+ });
+});