diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 76842cbf5..055ab8df8 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -5,6 +5,7 @@ iD.Graph = function(other, mutable) { var base = other.base(); this.entities = _.assign(Object.create(base.entities), other.entities); this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays); + this._parentRels = _.assign(Object.create(base.parentRels), other._parentRels); this.inherited = true; } else { @@ -17,11 +18,11 @@ iD.Graph = function(other, mutable) { } this.entities = Object.create({}); this._parentWays = Object.create({}); + this._parentRels = Object.create({}); this.rebase(other || {}); } this.transients = {}; - this._parentRels = {}; this._childNodes = {}; if (!mutable) { @@ -55,23 +56,9 @@ iD.Graph.prototype = { }, parentRelations: function(entity) { + return _.map(this._parentRels[entity.id], _.bind(this.entity, this)); var ent, id, parents; - if (!this._parentRels.calculated) { - for (var i in this.entities) { - ent = this.entities[i]; - if (ent && ent.type === 'relation') { - for (var j = 0; j < ent.members.length; j++) { - id = ent.members[j].id; - parents = this._parentRels[id] = this._parentRels[id] || []; - if (parents.indexOf(ent) < 0) { - parents.push(ent); - } - } - } - } - this._parentRels.calculated = true; - } return this._parentRels[entity.id] || []; }, @@ -91,7 +78,8 @@ iD.Graph.prototype = { base: function() { return { 'entities': iD.util.getPrototypeOf(this.entities), - 'parentWays': iD.util.getPrototypeOf(this._parentWays) + 'parentWays': iD.util.getPrototypeOf(this._parentWays), + 'parentRels': iD.util.getPrototypeOf(this._parentRels) }; }, @@ -100,20 +88,26 @@ iD.Graph.prototype = { // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. rebase: function(entities) { - var i; + var i, keys; // Merging of data only needed if graph is the base graph if (!this.inherited) { _.defaults(this.base().entities, entities); for (i in entities) { - this._updateCalculated(undefined, entities[i], this.base().parentWays); + this._updateCalculated(undefined, entities[i], this.base().parentWays, this.base().parentRels); } } - var keys = Object.keys(this._parentWays); + keys = Object.keys(this._parentWays); for (i = 0; i < keys.length; i++) { this._parentWays[keys[i]] = _.unique((this._parentWays[keys[i]] || []) .concat(this.base().parentWays[keys[i]] || [])); } + + keys = Object.keys(this._parentRels); + for (i = 0; i < keys.length; i++) { + this._parentRels[keys[i]] = _.unique((this._parentRels[keys[i]] || []) + .concat(this.base().parentRels[keys[i]] || [])); + } }, // Updates calculated properties (parentWays, parentRels) for the specified change @@ -123,7 +117,7 @@ iD.Graph.prototype = { parentRels = parentRels || this._parentRels; var type = entity && entity.type || oldentity && oldentity.type, - removed, added, ways, i; + removed, added, ways, rels, i; if (type === 'way') { @@ -151,8 +145,25 @@ iD.Graph.prototype = { } else if (type === 'relation') { - // TODO: iterate over members - + // Update parentRels + if (oldentity && entity) { + removed = _.difference(oldentity.members, entity.members); + added = _.difference(entity.members, oldentity); + } else if (oldentity) { + removed = oldentity.members; + added = []; + } else if (entity) { + removed = []; + added = entity.members; + } + for (i = 0; i < removed.length; i++) { + parentRels[removed[i].id] = _.without(parentRels[removed[i].id], oldentity.id); + } + for (i = 0; i < added.length; i++) { + rels = _.without(parentRels[added[i].id], entity.id); + rels.push(entity.id); + parentRels[added[i].id] = rels; + } } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 61b620ef3..9d918ae04 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -85,35 +85,49 @@ describe('iD.Graph', function() { var n = iD.Node({id: 'n'}), w1 = iD.Way({id: 'w1', nodes: ['n']}), w2 = iD.Way({id: 'w2', nodes: ['n']}), - w3 = iD.Way({id: 'w3', nodes: ['n']}), graph = iD.Graph([n, w1]); - var graph2 = graph.replace(w2); + graph.rebase({ 'w2': w2 }); + expect(graph.parentWays(n)).to.eql([w1, w2]); + expect(graph._parentWays.hasOwnProperty('n')).to.be.false; + }); + + it("updates parentWays for nodes with modified parentWays", function () { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + w2 = iD.Way({id: 'w2', nodes: ['n']}), + w3 = iD.Way({id: 'w3', nodes: ['n']}), + graph = iD.Graph([n, w1]), + graph2 = graph.replace(w2); graph.rebase({ 'w3': w3 }); graph2.rebase({ 'w3': w3 }); expect(graph2.parentWays(n)).to.eql([w1, w2, w3]); }); - it("only sets non-base parentWays when necessary", function () { - var n = iD.Node({id: 'n'}), - w1 = iD.Way({id: 'w1', nodes: ['n']}), - graph = iD.Graph([n, w1]); - graph.rebase({ 'w1': w1 }); - expect(graph._parentWays.hasOwnProperty('n')).to.be.false; - expect(graph.parentWays(n)).to.eql([w1]); - }); - - xit("updates parentRelations", function () { + it("updates parentRelations", function () { var n = iD.Node({id: 'n'}), r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), graph = iD.Graph([n, r1]); - graph.parentRelations(n); graph.rebase({'r2': r2}); expect(graph.parentRelations(n)).to.eql([r1, r2]); + expect(graph._parentRels.hasOwnProperty('n')).to.be.false; + }); + + it("updates parentWays for nodes with modified parentWays", function () { + var n = iD.Node({id: 'n'}), + r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), + r3 = iD.Relation({id: 'r3', members: [{id: 'n'}]}), + graph = iD.Graph([n, r1]), + graph2 = graph.replace(r2); + + graph.rebase({'r3': r3}); + graph2.rebase({'r3': r3}); + expect(graph2.parentRelations(n)).to.eql([r1, r2, r3]); }); });