diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 2705fd029..76842cbf5 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -5,15 +5,19 @@ 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.rebase(other.base(), other.entities); + this.inherited = true; } else { - this.entities = other || Object.create({}); - this._parentWays = Object.create({}); - - for (var i in this.entities) { - this._updateCalculated(undefined, this.entities[i]); + if (_.isArray(other)) { + var entities = {}; + for (var i = 0; i < other.length; i++) { + entities[other[i].id] = other[i]; + } + other = entities; } + this.entities = Object.create({}); + this._parentWays = Object.create({}); + this.rebase(other || {}); } this.transients = {}; @@ -43,25 +47,7 @@ iD.Graph.prototype = { }, parentWays: function(entity) { - var ent, id, parents; - - if (!this._parentWays.calculated) { - for (var i in this.entities) { - ent = this.entities[i]; - if (ent && ent.type === 'way') { - for (var j = 0; j < ent.nodes.length; j++) { - id = ent.nodes[j]; - parents = this._parentWays[id] = this._parentWays[id] || []; - if (parents.indexOf(ent) < 0) { - parents.push(ent); - } - } - } - } - this._parentWays.calculated = true; - } - - return this._parentWays[entity.id] || []; + return _.map(this._parentWays[entity.id], _.bind(this.entity, this)); }, isPoi: function(entity) { @@ -113,13 +99,31 @@ iD.Graph.prototype = { // is used only during the history operation that merges newly downloaded // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. - rebase: function(base, entities) { + rebase: function(entities) { + var i; + // 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); + } + } + + var 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]] || [])); + } }, - _updateCalculated: function(oldentity, entity) { + // Updates calculated properties (parentWays, parentRels) for the specified change + _updateCalculated: function(oldentity, entity, parentWays, parentRels) { + + parentWays = parentWays || this._parentWays; + parentRels = parentRels || this._parentRels; var type = entity && entity.type || oldentity && oldentity.type, - removed, added, parentWays, i; + removed, added, ways, i; if (type === 'way') { @@ -136,12 +140,12 @@ iD.Graph.prototype = { added = entity.nodes; } for (i = 0; i < removed.length; i++) { - this._parentWays[removed[i]] = _.without(this._parentWays[removed[i]], oldentity.id); + parentWays[removed[i]] = _.without(parentWays[removed[i]], oldentity.id); } for (i = 0; i < added.length; i++) { - parentWays = _.without(this._parentWays[added[i]], entity.id); - parentWays.push(entity.id); - this._parentWays[added[i]] = parentWays; + ways = _.without(parentWays[added[i]], entity.id); + ways.push(entity.id); + parentWays[added[i]] = ways; } } else if (type === 'node') { @@ -199,9 +203,12 @@ iD.Graph.prototype = { }, difference: function (graph) { - var result = [], entity, oldentity, id; + var result = [], + keys = Object.keys(this.entities), + entity, oldentity, id, i; - for (id in this.entities) { + for (i = 0; i < keys.length; i++) { + id = keys[i]; entity = this.entities[id]; oldentity = graph.entities[id]; if (entity !== oldentity) { @@ -223,7 +230,8 @@ iD.Graph.prototype = { } } - for (id in graph.entities) { + keys = Object.keys(graph.entities); + for (i = 0; i < keys.length; i++) { entity = graph.entities[id]; if (entity && !this.entities.hasOwnProperty(id)) { result.push(id); @@ -235,25 +243,25 @@ iD.Graph.prototype = { }, modified: function() { - var result = []; + var result = [], base = this.base().entities; _.each(this.entities, function(entity, id) { - if (entity && entity.modified()) result.push(id); + if (entity && base[id]) result.push(id); }); return result; }, created: function() { - var result = []; + var result = [], base = this.base().entities; _.each(this.entities, function(entity, id) { - if (entity && entity.created()) result.push(id); + if (entity && !base[id]) result.push(id); }); return result; }, deleted: function() { - var result = []; + var result = [], base = this.base().entities; _.each(this.entities, function(entity, id) { - if (!entity) result.push(id); + if (!entity && base[id]) result.push(id); }); return result; } diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 95e499ef2..3630bb915 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -30,16 +30,8 @@ iD.History = function() { }, merge: function (graph) { - var base = stack[0].graph.base(); - - _.defaults(base.entities, graph.entities); - for (var i in graph.entities) { - base.parentWays[i] = _.unique((base.parentWays[id] || []).concat(graph._parentWays[i] || [])); - } - _.defaults(base.parentWays, graph._parentWays); - - for (i = 1; i < stack.length; i++) { - stack[i].graph.rebase(base); + for (var i = 0; i < stack.length; i++) { + stack[i].graph.rebase(graph.base().entities); } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index fa83a3a03..61b620ef3 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -28,7 +28,7 @@ describe('iD.Graph', function() { it("rebases on other's base", function () { var base = iD.Graph(), graph = iD.Graph(base); - expect(graph.base()).to.equal(base.base()); + expect(graph.base().entities).to.equal(base.base().entities); }); it("freezes by default", function () { @@ -81,16 +81,27 @@ describe('iD.Graph', function() { expect(graph.entities).not.to.have.ownProperty('n'); }); - xit("updates parentWays", function () { + it("updates 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]); - graph.parentWays(n); - graph.rebase({'w2': w2}); + var graph2 = graph.replace(w2); + graph.rebase({ 'w3': w3 }); + graph2.rebase({ 'w3': w3 }); - expect(graph.parentWays(n)).to.eql([w1, w2]); + 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 () { @@ -244,18 +255,18 @@ describe('iD.Graph', function() { describe("#modified", function () { it("returns an Array of ids of modified entities", function () { - var node1 = iD.Node({id: 'n1', _updated: true}), - node2 = iD.Node({id: 'n2'}), - graph = iD.Graph([node1, node2]); - expect(graph.modified()).to.eql([node1.id]); + var node = iD.Node({id: 'n1'}), + node_ = iD.Node({id: 'n1'}), + graph = iD.Graph([node]).replace(node_); + expect(graph.modified()).to.eql([node.id]); }); }); describe("#created", function () { it("returns an Array of ids of created entities", function () { - var node1 = iD.Node({id: 'n-1', _updated: true}), + var node1 = iD.Node({id: 'n-1'}), node2 = iD.Node({id: 'n2'}), - graph = iD.Graph([node1, node2]); + graph = iD.Graph([node2]).replace(node1); expect(graph.created()).to.eql([node1.id]); }); }); @@ -270,7 +281,7 @@ describe('iD.Graph', function() { it("doesn't include created entities that were subsequently deleted", function () { var node = iD.Node(), - graph = iD.Graph([node]).remove(node); + graph = iD.Graph().replace(node).remove(node); expect(graph.deleted()).to.eql([]); }); });