Add entity copy methods

This commit is contained in:
Bryan Housel
2014-12-21 12:51:23 -05:00
parent d9294e3e04
commit dae0d2d55e
6 changed files with 194 additions and 1 deletions

View File

@@ -44,7 +44,11 @@ iD.Entity.prototype = {
var source = sources[i];
for (var prop in source) {
if (Object.prototype.hasOwnProperty.call(source, prop)) {
this[prop] = source[prop];
if (source[prop] === undefined) {
delete this[prop];
} else {
this[prop] = source[prop];
}
}
}
}
@@ -65,6 +69,23 @@ iD.Entity.prototype = {
return this;
},
copy: function(attrs) {
var clone = {},
omit = {};
_.each(['tags', 'loc', 'nodes', 'members'], function(prop) {
if (this.hasOwnProperty(prop)) clone[prop] = _.cloneDeep(this[prop]);
}, this);
_.each(['id', 'user', 'v', 'version'], function(prop) {
omit[prop] = undefined;
});
// Returns an array so that we can support deep copying ways and relations.
// The first array element will contain this.copy, followed by any descendants.
return [ iD.Entity(this, _.extend(clone, (attrs || {}), omit)) ];
},
osmId: function() {
return iD.Entity.id.toOSM(this.id);
},

View File

@@ -20,6 +20,32 @@ _.extend(iD.Relation.prototype, {
type: 'relation',
members: [],
copy: function(attrs, deep, resolver) {
var fn = iD.Entity.prototype.copy;
if (deep && resolver && this.isComplete(resolver)) {
var members = [],
descendants = [],
replacements = {},
i, oldmember, oldid, newid, child;
for (i = 0; i < this.members.length; i++) {
oldmember = this.members[i];
oldid = oldmember.id;
newid = replacements[oldid];
if (!newid) {
child = resolver.entity(oldid).copy({}, true, resolver);
newid = replacements[oldid] = child[0].id;
descendants = child.concat(descendants);
}
members.push({id: newid, type: oldmember.type, role: oldmember.role});
}
return fn.call(this, _.extend(attrs, {members: members})).concat(descendants);
} else {
return fn.call(this, attrs);
}
},
extent: function(resolver, memo) {
return resolver.transient(this, 'extent', function() {
if (memo && memo[this.id]) return iD.geo.Extent();

View File

@@ -12,6 +12,31 @@ _.extend(iD.Way.prototype, {
type: 'way',
nodes: [],
copy: function(attrs, deep, resolver) {
var fn = iD.Entity.prototype.copy;
if (deep && resolver) {
var nodes = [],
descendants = [],
replacements = {},
i, oldid, newid, child;
for (i = 0; i < this.nodes.length; i++) {
oldid = this.nodes[i];
newid = replacements[oldid];
if (!newid) {
child = resolver.entity(oldid).copy();
newid = replacements[oldid] = child[0].id;
descendants = child.concat(descendants);
}
nodes.push(newid);
}
return fn.call(this, _.extend(attrs, {nodes: nodes})).concat(descendants);
} else {
return fn.call(this, attrs);
}
},
extent: function(resolver) {
return resolver.transient(this, 'extent', function() {
var extent = iD.geo.Extent();

View File

@@ -36,6 +36,32 @@ describe('iD.Entity', function () {
});
});
describe("#copy", function () {
it("returns a new Entity", function () {
var a = iD.Entity(),
result = a.copy();
expect(result).to.have.length(1);
expect(result[0]).to.be.an.instanceof(iD.Entity);
expect(a).not.to.equal(result[0]);
});
it("resets 'id', 'user', 'v', and 'version' properties", function () {
var a = iD.Entity({id: 'n1234', version: 10, v: 4, user: 'bot-mode'}),
b = a.copy()[0];
expect(b.isNew()).to.be.ok;
expect(b.version).to.be.undefined;
expect(b.v).to.be.undefined;
expect(b.user).to.be.undefined;
});
it("copies tags", function () {
var a = iD.Entity({id: 'n1234', version: 10, user: 'test', tags: {foo: 'foo'}}),
b = a.copy()[0];
expect(b.tags).not.to.equal(a.tags);
expect(b.tags).to.deep.equal(a.tags);
});
});
describe("#update", function () {
it("returns a new Entity", function () {
var a = iD.Entity(),

View File

@@ -26,6 +26,54 @@ describe('iD.Relation', function () {
expect(iD.Relation({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
});
describe("#copy", function () {
it("returns a new Relation", function () {
var r1 = iD.Relation({id: 'r1'}),
result = r1.copy(),
r2 = result[0];
expect(result).to.have.length(1);
expect(r2).to.be.an.instanceof(iD.Relation);
expect(r1).not.to.equal(r2);
});
it("keeps same members when deep = false", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
c = iD.Node({id: 'c'}),
w1 = iD.Way({id: 'w1', nodes: ['a','b','c','a']}),
r1 = iD.Relation({id: 'r1', members: [{id: 'w1', role: 'outer'}]}),
graph = iD.Graph([a, b, c, w1, r1]),
result = r1.copy(),
r2 = result[0];
expect(result).to.have.length(1);
expect(r1.members).not.to.equal(r2.members);
expect(r1.members).to.deep.equal(r2.members);
});
it("makes new members when deep = true", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
c = iD.Node({id: 'c'}),
w1 = iD.Way({id: 'w1', nodes: ['a','b','c','a']}),
r1 = iD.Relation({id: 'r1', members: [{id: 'w1', role: 'outer'}]}),
graph = iD.Graph([a, b, c, w1, r1]),
result = r1.copy({}, true, graph),
r2 = result[0];
expect(result).to.have.length(5);
expect(result[0]).to.be.an.instanceof(iD.Relation);
expect(result[1]).to.be.an.instanceof(iD.Way);
expect(result[2]).to.be.an.instanceof(iD.Node);
expect(result[3]).to.be.an.instanceof(iD.Node);
expect(result[4]).to.be.an.instanceof(iD.Node);
expect(r2.members[0].id).not.to.equal(r1.members[0].id);
expect(r2.members[0].role).to.equal(r1.members[0].role);
});
});
describe("#extent", function () {
it("returns the minimal extent containing the extents of all members", function () {
var a = iD.Node({loc: [0, 0]}),

View File

@@ -26,6 +26,53 @@ describe('iD.Way', function() {
expect(iD.Way({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
});
describe("#copy", function () {
it("returns a new Way", function () {
var w1 = iD.Way({id: 'w1'}),
result = w1.copy(),
w2 = result[0];
expect(result).to.have.length(1);
expect(w2).to.be.an.instanceof(iD.Way);
expect(w1).not.to.equal(w2);
});
it("keeps same nodes when deep = false", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
c = iD.Node({id: 'c'}),
w1 = iD.Entity({id: 'w1', nodes: ['a','b','c','a']}),
graph = iD.Graph([a, b, c, w1]),
result = w1.copy(),
w2 = result[0];
expect(result).to.have.length(1);
expect(w1.nodes).not.to.equal(w2.nodes);
expect(w1.nodes).to.deep.equal(w2.nodes);
});
it("makes new nodes when deep = true", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
c = iD.Node({id: 'c'}),
w1 = iD.Entity({id: 'w1', nodes: ['a','b','c','a']}),
graph = iD.Graph([a, b, c, w1]),
result = w1.copy({}, true, graph),
w2 = result[0];
expect(result).to.have.length(4);
expect(result[0]).to.be.an.instanceof(iD.Way);
expect(result[1]).to.be.an.instanceof(iD.Node);
expect(result[2]).to.be.an.instanceof(iD.Node);
expect(result[3]).to.be.an.instanceof(iD.Node);
expect(w2.nodes[0]).not.to.equal(w1.nodes[0]);
expect(w2.nodes[1]).not.to.equal(w1.nodes[1]);
expect(w2.nodes[2]).not.to.equal(w1.nodes[2]);
expect(w2.nodes[3]).to.equal(w2.nodes[0]);
});
});
describe("#first", function () {
it("returns the first node", function () {
expect(iD.Way({nodes: ['a', 'b', 'c']}).first()).to.equal('a');