Merge branch 'master' of github.com:systemed/iD

This commit is contained in:
Saman Bemel-Benrud
2013-01-30 19:09:25 -05:00
10 changed files with 401 additions and 113 deletions
+1 -1
View File
@@ -124,7 +124,7 @@ iD.Connection = function() {
}
}
return iD.Graph(entities);
return entities;
}
function authenticated() {
+156 -69
View File
@@ -1,19 +1,30 @@
iD.Graph = function(entities, mutable) {
if (!(this instanceof iD.Graph)) return new iD.Graph(entities, mutable);
iD.Graph = function(other, mutable) {
if (!(this instanceof iD.Graph)) return new iD.Graph(other, mutable);
if (other instanceof iD.Graph) {
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;
if (_.isArray(entities)) {
this.entities = {};
for (var i = 0; i < entities.length; i++) {
this.entities[entities[i].id] = entities[i];
}
} else {
this.entities = entities || {};
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._parentRels = Object.create({});
this.rebase(other || {});
}
this.transients = {};
this._parentWays = {};
this._parentRels = {};
this._childNodes = {};
this.getEntity = _.bind(this.entity, this);
if (!mutable) {
this.freeze();
@@ -38,51 +49,21 @@ 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], this.getEntity);
},
isPoi: function(entity) {
return this.parentWays(entity).length === 0;
var parentWays = this._parentWays[entity.id];
return !parentWays || parentWays.length === 0;
},
isShared: function(entity) {
var parentWays = this._parentWays[entity.id];
return parentWays && parentWays.length > 1;
},
parentRelations: function(entity) {
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] || [];
return _.map(this._parentRels[entity.id], this.getEntity);
},
childNodes: function(entity) {
@@ -97,30 +78,132 @@ iD.Graph.prototype = {
return (this._childNodes[entity.id] = nodes);
},
merge: function(graph) {
return this.update(function () {
_.defaults(this.entities, graph.entities);
});
base: function() {
return {
'entities': iD.util.getPrototypeOf(this.entities),
'parentWays': iD.util.getPrototypeOf(this._parentWays),
'parentRels': iD.util.getPrototypeOf(this._parentRels)
};
},
// Unlike other graph methods, rebase mutates in place. This is because it
// 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(entities) {
var base = this.base(),
i, k, child, id, keys;
// Merging of data only needed if graph is the base graph
if (!this.inherited) {
for (i in entities) {
if (!base.entities[i]) {
base.entities[i] = entities[i];
this._updateCalculated(undefined, entities[i],
base.parentWays, base.parentRels);
}
}
}
keys = Object.keys(this._parentWays);
for (i = 0; i < keys.length; i++) {
child = keys[i];
if (base.parentWays[child]) {
for (k = 0; k < base.parentWays[child].length; k++) {
id = base.parentWays[child][k];
if (this.entity(id) && !_.contains(this._parentWays[child], id)) {
this._parentWays[child].push(id);
}
}
}
}
keys = Object.keys(this._parentRels);
for (i = 0; i < keys.length; i++) {
child = keys[i];
if (base.parentRels[child]) {
for (k = 0; k < base.parentRels[child].length; k++) {
id = base.parentRels[child][k];
if (this.entity(id) && !_.contains(this._parentRels[child], id)) {
this._parentRels[child].push(id);
}
}
}
}
},
// 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, ways, rels, i;
if (type === 'way') {
// Update parentWays
if (oldentity && entity) {
removed = _.difference(oldentity.nodes, entity.nodes);
added = _.difference(entity.nodes, oldentity.nodes);
} else if (oldentity) {
removed = oldentity.nodes;
added = [];
} else if (entity) {
removed = [];
added = entity.nodes;
}
for (i = 0; i < removed.length; i++) {
parentWays[removed[i]] = _.without(parentWays[removed[i]], oldentity.id);
}
for (i = 0; i < added.length; i++) {
ways = _.without(parentWays[added[i]], entity.id);
ways.push(entity.id);
parentWays[added[i]] = ways;
}
} else if (type === 'node') {
} else if (type === 'relation') {
// 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;
}
}
},
replace: function(entity) {
return this.update(function () {
this._updateCalculated(this.entities[entity.id], entity);
this.entities[entity.id] = entity;
});
},
remove: function(entity) {
return this.update(function () {
if (entity.created()) {
delete this.entities[entity.id];
} else {
this.entities[entity.id] = undefined;
}
this._updateCalculated(entity, undefined);
this.entities[entity.id] = undefined;
});
},
update: function() {
var graph = this.frozen ? iD.Graph(_.clone(this.entities), true) : this;
var graph = this.frozen ? iD.Graph(this, true) : this;
for (var i = 0; i < arguments.length; i++) {
arguments[i].call(graph, graph);
@@ -133,7 +216,6 @@ iD.Graph.prototype = {
this.frozen = true;
if (iD.debug) {
Object.freeze(this);
Object.freeze(this.entities);
}
@@ -153,9 +235,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) {
@@ -177,7 +262,9 @@ iD.Graph.prototype = {
}
}
for (id in graph.entities) {
keys = Object.keys(graph.entities);
for (i = 0; i < keys.length; i++) {
id = keys[i];
entity = graph.entities[id];
if (entity && !this.entities.hasOwnProperty(id)) {
result.push(id);
@@ -189,25 +276,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;
}
+2 -2
View File
@@ -29,9 +29,9 @@ iD.History = function() {
return stack[index].graph;
},
merge: function (graph) {
merge: function (entities) {
for (var i = 0; i < stack.length; i++) {
stack[i].graph = stack[i].graph.merge(graph);
stack[i].graph.rebase(entities);
}
},
+4 -3
View File
@@ -64,7 +64,7 @@ iD.Map = function() {
for (var i = 0; i < parents.length; i++) {
var parent = parents[i];
if (only[parent.id] === undefined) {
only[parent.id] = graph.entity(parent.id);
only[parent.id] = parent;
addParents(graph.parentRelations(parent));
}
}
@@ -123,7 +123,7 @@ iD.Map = function() {
function connectionLoad(err, result) {
history.merge(result);
redraw(Object.keys(result.entities));
redraw(Object.keys(result));
}
function zoomPan() {
@@ -165,7 +165,8 @@ iD.Map = function() {
}
function resetTransform() {
if (!surface.style(transformProp)) return false;
var prop = surface.style(transformProp);
if (!prop || prop === 'none') return false;
surface.style(transformProp, '');
tilegroup.style(transformProp, '');
return true;
+1 -1
View File
@@ -36,7 +36,7 @@ iD.svg.Vertices = function(projection) {
groups.attr('transform', iD.svg.PointTransform(projection))
.call(iD.svg.TagClasses())
.call(iD.svg.MemberClasses(graph))
.classed('shared', function(entity) { return graph.parentWays(entity).length > 1; });
.classed('shared', function(entity) { return graph.isShared(entity); });
// Selecting the following implicitly
// sets the data (vertix entity) on the elements
+5 -1
View File
@@ -16,7 +16,7 @@ iD.ui.contributors = function(map) {
var l = selection
.select('.contributor-list')
.selectAll('a.user-link')
.data(subset);
.data(subset, function(d) { return d; });
l.enter().append('a')
@@ -43,6 +43,10 @@ iD.ui.contributors = function(map) {
ext[1][0], ext[1][1]];
})
.text(' and ' + (u.length - limit) + ' others');
} else {
selection
.select('.contributor-count')
.html('');
}
if (!u.length) {
+2
View File
@@ -74,3 +74,5 @@ iD.util.getStyle = function(selector) {
}
}
};
iD.util.getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; };
+4 -4
View File
@@ -26,24 +26,24 @@ describe('iD.Connection', function () {
c.loadFromURL('data/node.xml', done);
});
it('returns a graph', function (done) {
it('returns an object', function (done) {
c.loadFromURL('data/node.xml', function (err, graph) {
expect(err).to.not.be.ok;
expect(graph).to.be.instanceOf(iD.Graph);
expect(typeof graph).to.eql('object');
done();
});
});
it('parses a node', function (done) {
c.loadFromURL('data/node.xml', function (err, graph) {
expect(graph.entity('n356552551')).to.be.instanceOf(iD.Entity);
expect(graph.n356552551).to.be.instanceOf(iD.Entity);
done();
});
});
it('parses a way', function (done) {
c.loadFromURL('data/way.xml', function (err, graph) {
expect(graph.entity('w19698713')).to.be.instanceOf(iD.Entity);
expect(graph.w19698713).to.be.instanceOf(iD.Entity);
done();
});
});
+221 -25
View File
@@ -1,25 +1,164 @@
describe('iD.Graph', function() {
it("can be constructed with an entities Object", function () {
var entity = iD.Entity(),
graph = iD.Graph({'n-1': entity});
expect(graph.entity('n-1')).to.equal(entity);
});
it("can be constructed with an entities Array", function () {
var entity = iD.Entity(),
graph = iD.Graph([entity]);
expect(graph.entity(entity.id)).to.equal(entity);
});
if (iD.debug) {
it("is frozen", function () {
expect(Object.isFrozen(iD.Graph())).to.be.true;
describe("constructor", function () {
it("accepts an entities Object", function () {
var entity = iD.Entity(),
graph = iD.Graph({'n-1': entity});
expect(graph.entity('n-1')).to.equal(entity);
});
it("freezes entities", function () {
expect(Object.isFrozen(iD.Graph().entities)).to.be.true;
it("accepts an entities Array", function () {
var entity = iD.Entity(),
graph = iD.Graph([entity]);
expect(graph.entity(entity.id)).to.equal(entity);
});
}
it("accepts a Graph", function () {
var entity = iD.Entity(),
graph = iD.Graph(iD.Graph([entity]));
expect(graph.entity(entity.id)).to.equal(entity);
});
it("copies other's entities", function () {
var entity = iD.Entity(),
base = iD.Graph([entity]),
graph = iD.Graph(base);
expect(graph.entities).not.to.equal(base.entities);
});
it("rebases on other's base", function () {
var base = iD.Graph(),
graph = iD.Graph(base);
expect(graph.base().entities).to.equal(base.base().entities);
});
it("freezes by default", function () {
expect(iD.Graph().frozen).to.be.true;
});
it("remains mutable if passed true as second argument", function () {
expect(iD.Graph([], true).frozen).not.to.be.true;
});
});
describe("#freeze", function () {
it("sets the frozen flag", function () {
expect(iD.Graph([], true).freeze().frozen).to.be.true;
});
if (iD.debug) {
it("freezes entities", function () {
expect(Object.isFrozen(iD.Graph().entities)).to.be.true;
});
}
});
describe("#rebase", function () {
it("preserves existing entities", function () {
var node = iD.Node({id: 'n'}),
graph = iD.Graph([node]);
graph.rebase({});
expect(graph.entity('n')).to.equal(node);
});
it("includes new entities", function () {
var node = iD.Node({id: 'n'}),
graph = iD.Graph();
graph.rebase({'n': node});
expect(graph.entity('n')).to.equal(node);
});
it("gives precedence to existing entities", function () {
var a = iD.Node({id: 'n'}),
b = iD.Node({id: 'n'}),
graph = iD.Graph([a]);
graph.rebase({'n': b});
expect(graph.entity('n')).to.equal(a);
});
it("inherits entities from base prototypally", function () {
var graph = iD.Graph();
graph.rebase({'n': iD.Node()});
expect(graph.entities).not.to.have.ownProperty('n');
});
it("updates parentWays", function () {
var n = iD.Node({id: 'n'}),
w1 = iD.Way({id: 'w1', nodes: ['n']}),
w2 = iD.Way({id: 'w2', nodes: ['n']}),
graph = iD.Graph([n, w1]);
graph.rebase({ 'w2': w2 });
expect(graph.parentWays(n)).to.eql([w1, w2]);
expect(graph._parentWays.hasOwnProperty('n')).to.be.false;
});
it("avoids adding duplicate parentWays", 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(n)).to.eql([w1]);
});
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("avoids re-adding removed parentWays", function() {
var n = iD.Node({id: 'n'}),
w1 = iD.Way({id: 'w1', nodes: ['n']}),
graph = iD.Graph([n, w1]),
graph2 = graph.remove(w1);
graph.rebase({ 'w1': w1 });
graph2.rebase({ 'w1': w1 });
expect(graph2.parentWays(n)).to.eql([]);
});
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.rebase({'r2': r2});
expect(graph.parentRelations(n)).to.eql([r1, r2]);
expect(graph._parentRels.hasOwnProperty('n')).to.be.false;
});
it("avoids re-adding removed parentRels", function() {
var n = iD.Node({id: 'n'}),
r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}),
graph = iD.Graph([n, r1]),
graph2 = graph.remove(r1);
graph.rebase({ 'w1': r1 });
graph2.rebase({ 'w1': r1 });
expect(graph2.parentWays(n)).to.eql([]);
});
it("updates parentRels 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]);
});
});
describe("#remove", function () {
it("returns a new graph", function () {
@@ -40,6 +179,20 @@ describe('iD.Graph', function() {
graph = iD.Graph([node]);
expect(graph.remove(node).entity(node.id)).to.be.undefined;
});
it("removes the entity as a parentWay", function () {
var node = iD.Node({id: 'n' }),
w1 = iD.Way({id: 'w', nodes: ['n']}),
graph = iD.Graph([node, w1]);
expect(graph.remove(w1).parentWays(node)).to.eql([]);
});
it("removes the entity as a parentRelation", function () {
var node = iD.Node({id: 'n' }),
r1 = iD.Relation({id: 'w', members: [{id: 'n' }]}),
graph = iD.Graph([node, r1]);
expect(graph.remove(r1).parentRelations(node)).to.eql([]);
});
});
describe("#replace", function () {
@@ -62,6 +215,49 @@ describe('iD.Graph', function() {
graph = iD.Graph([node1]);
expect(graph.replace(node2).entity(node2.id)).to.equal(node2);
});
it("adds parentWays", function () {
var node = iD.Node({id: 'n' }),
w1 = iD.Way({id: 'w', nodes: ['n']}),
graph = iD.Graph([node]);
expect(graph.replace(w1).parentWays(node)).to.eql([w1]);
});
it("removes parentWays", function () {
var node = iD.Node({id: 'n' }),
w1 = iD.Way({id: 'w', nodes: ['n']}),
graph = iD.Graph([node, w1]);
expect(graph.remove(w1).parentWays(node)).to.eql([]);
});
it("doesn't add duplicate parentWays", function () {
var node = iD.Node({id: 'n' }),
w1 = iD.Way({id: 'w', nodes: ['n']}),
graph = iD.Graph([node, w1]);
expect(graph.replace(w1).parentWays(node)).to.eql([w1]);
});
it("adds parentRels", function () {
var node = iD.Node({id: 'n' }),
r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}),
graph = iD.Graph([node]);
expect(graph.replace(r1).parentRelations(node)).to.eql([r1]);
});
it("removes parentRelations", function () {
var node = iD.Node({id: 'n' }),
r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}),
graph = iD.Graph([node, r1]);
expect(graph.remove(r1).parentRelations(node)).to.eql([]);
});
it("doesn't add duplicate parentRelations", function () {
var node = iD.Node({id: 'n' }),
r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}),
graph = iD.Graph([node, r1]);
expect(graph.replace(r1).parentRelations(node)).to.eql([r1]);
});
});
describe("#update", function () {
@@ -159,18 +355,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]);
});
});
@@ -185,7 +381,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([]);
});
});
+5 -7
View File
@@ -153,17 +153,15 @@ describe("iD.History", function () {
it("includes modified entities", function () {
var node1 = iD.Node({id: "n1"}),
node2 = node1.update({}),
graph = iD.Graph([node1]);
history.merge(graph);
node2 = node1.update({});
history.merge({ n1: node1});
history.perform(function (graph) { return graph.replace(node2); });
expect(history.changes().modified).to.eql([node2]);
});
it("includes deleted entities", function () {
var node = iD.Node({id: "n1"}),
graph = iD.Graph([node]);
history.merge(graph);
var node = iD.Node({id: "n1"});
history.merge({ n1: node });
history.perform(function (graph) { return graph.remove(node); });
expect(history.changes().deleted).to.eql([node]);
});
@@ -189,7 +187,7 @@ describe("iD.History", function () {
it("is the sum of all types of changes", function() {
var node1 = iD.Node({id: "n1"}),
node2 = iD.Node();
history.merge(iD.Graph([node1]));
history.merge({ n1: node1 });
history.perform(function (graph) { return graph.remove(node1); });
expect(history.numChanges()).to.eql(1);
history.perform(function (graph) { return graph.replace(node2); });