Make Graph#entity strict

Use Graph#hasEntity for the previous behavior.
This commit is contained in:
John Firebaugh
2013-04-24 09:27:37 -07:00
parent 78aee5b6aa
commit c50c3121d8
26 changed files with 104 additions and 74 deletions

View File

@@ -59,7 +59,7 @@ iD.actions.Circularize = function(wayId, projection, count) {
};
action.disabled = function(graph) {
if (!graph.entity(wayId, true).isClosed())
if (!graph.entity(wayId).isClosed())
return 'not_closed';
};

View File

@@ -7,9 +7,8 @@ iD.actions.DeleteMultiple = function(ids) {
};
ids.forEach(function(id) {
var entity = graph.entity(id);
if (entity) { // It may have been deleted aready.
graph = actions[entity.type](id)(graph);
if (graph.hasEntity(id)) { // It may have been deleted aready.
graph = actions[graph.entity(id).type](id)(graph);
}
});

View File

@@ -128,7 +128,7 @@ iD.actions.Orthogonalize = function(wayId, projection) {
};
action.disabled = function(graph) {
if (!graph.entity(wayId, true).isClosed())
if (!graph.entity(wayId).isClosed())
return 'not_closed';
};

View File

@@ -60,7 +60,7 @@ iD.actions.Split = function(nodeId, newWayIds) {
j;
for (j = 0; j < relation.members.length; j++) {
var entity = graph.entity(relation.members[j].id);
var entity = graph.hasEntity(relation.members[j].id);
if (entity && entity.type === 'way' && entity.contains(last)) {
break;
}

View File

@@ -115,7 +115,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
drawWay.add = function(loc) {
// prevent duplicate nodes
var last = context.entity(way.nodes[way.nodes.length - (isArea ? 2 : 1)]);
var last = context.hasEntity(way.nodes[way.nodes.length - (isArea ? 2 : 1)]);
if (last && last.loc[0] === loc[0] && last.loc[1] === loc[1]) return;
var newNode = iD.Node({loc: loc});
@@ -166,9 +166,8 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
context.map().dblclickEnable(true);
}, 1000);
var way = context.entity(wayId);
if (way) {
context.enter(iD.modes.Select(context, [way.id]).newFeature(true));
if (context.hasEntity(wayId)) {
context.enter(iD.modes.Select(context, [wayId]).newFeature(true));
} else {
context.enter(iD.modes.Browse(context));
}

View File

@@ -48,7 +48,7 @@ iD.behavior.Hash = function(context) {
});
context.map().on('drawn.hash', function() {
if (!context.entity(id)) return;
if (!context.hasEntity(id)) return;
selectoff();
context.enter(iD.modes.Select(context, [id]));
});

View File

@@ -112,12 +112,12 @@ iD.Difference = function(base, head) {
diff = _.difference(nh, nb);
for (i = 0; i < diff.length; i++) {
result[diff[i]] = head.entity(diff[i]);
result[diff[i]] = head.hasEntity(diff[i]);
}
diff = _.difference(nb, nh);
for (i = 0; i < diff.length; i++) {
result[diff[i]] = head.entity(diff[i]);
result[diff[i]] = head.hasEntity(diff[i]);
}
}

View File

@@ -31,10 +31,14 @@ iD.Graph = function(other, mutable) {
};
iD.Graph.prototype = {
entity: function(id, log) {
hasEntity: function(id) {
return this.entities[id];
},
entity: function(id) {
var entity = this.entities[id];
if (!entity && log && typeof Raven !== 'undefined') {
Raven.captureMessage('entity not found', {tags: {id: id, entity: entity, base: this.base().entities[id]}});
if (!entity) {
throw new Error('entity ' + id + ' not found');
}
return entity;
},

View File

@@ -15,7 +15,7 @@ _.extend(iD.Relation.prototype, {
extent: function(resolver) {
return resolver.transient(this, 'extent', function() {
return this.members.reduce(function(extent, member) {
member = resolver.entity(member.id);
member = resolver.hasEntity(member.id);
if (member) {
return extent.extend(member.extent(resolver));
} else {
@@ -142,7 +142,7 @@ _.extend(iD.Relation.prototype, {
isComplete: function(resolver) {
for (var i = 0; i < this.members.length; i++) {
if (!resolver.entity(this.members[i].id)) {
if (!resolver.hasEntity(this.members[i].id)) {
return false;
}
}
@@ -165,7 +165,7 @@ _.extend(iD.Relation.prototype, {
//
multipolygon: function(resolver) {
var members = this.members
.filter(function(m) { return m.type === 'way' && resolver.entity(m.id); })
.filter(function(m) { return m.type === 'way' && resolver.hasEntity(m.id); })
.map(function(m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.childNodes(resolver.entity(m.id)) }; });
function join(ways) {

View File

@@ -58,7 +58,7 @@ iD.Tree = function(graph) {
var created = diff.created().concat(queuedCreated);
modified = d3.values(diff.addParents(modified))
// some parents might be created, not modified
.filter(function(d) { return !!graph.entity(d.id); })
.filter(function(d) { return !!graph.hasEntity(d.id); })
.concat(queuedModified);
queuedCreated = [];
queuedModified = [];

View File

@@ -73,12 +73,16 @@ window.iD = function () {
context.intersects = history.intersects;
/* Graph */
context.hasEntity = function(id) {
return history.graph().hasEntity(id);
};
context.entity = function(id) {
return history.graph().entity(id);
};
context.geometry = function(id) {
return context.entity(id, true).geometry(history.graph());
return context.entity(id).geometry(history.graph());
};
/* Modes */

View File

@@ -110,7 +110,7 @@ iD.modes.Select = function(context, selection) {
function update() {
context.surface().call(radialMenu.close);
if (_.any(selection, function(id) { return !context.entity(id); })) {
if (_.any(selection, function(id) { return !context.hasEntity(id); })) {
// Exit mode if selected entity gets undone
context.enter(iD.modes.Browse(context));
}

View File

@@ -19,7 +19,7 @@ iD.svg.Vertices = function(projection, context) {
}
} else if (entity.type === 'relation') {
for (i = 0; i < entity.members.length; i++) {
var member = context.entity(entity.members[i].id);
var member = context.hasEntity(entity.members[i].id);
if (member) {
addChildVertices(member, klass);
}
@@ -30,7 +30,7 @@ iD.svg.Vertices = function(projection, context) {
}
function addSiblingAndChildVertices(id, klass) {
var entity = context.entity(id);
var entity = context.hasEntity(id);
if (entity && entity.type === 'node') {
visible[entity.id] = klass;
context.graph().parentWays(entity).forEach(function(entity) {

View File

@@ -77,11 +77,11 @@ iD.ui.Geocoder = function(context) {
function selectId(type, id) {
id = type[0] + id;
if (context.entity(id)) {
if (context.hasEntity(id)) {
context.enter(iD.modes.Select(context, [id]));
} else {
context.map().on('drawn.geocoder', function() {
if (!context.entity(id)) return;
if (!context.hasEntity(id)) return;
context.enter(iD.modes.Select(context, [id]));
});

View File

@@ -4,7 +4,7 @@ iD.ui.Inspector = function(context, entity) {
newFeature = false;
function changeTags(tags) {
var entity = context.entity(id);
var entity = context.hasEntity(id);
if (entity && !_.isEqual(entity.tags, tags)) {
context.perform(
iD.actions.ChangeTags(entity.id, tags),

View File

@@ -9,7 +9,7 @@ iD.ui.TagEditor = function(context, entity) {
tagList;
function update() {
var entity = context.entity(id);
var entity = context.hasEntity(id);
if (!entity) return;
tags = entity.tags;

View File

@@ -40,7 +40,7 @@ describe("iD.actions.Circularize", function () {
graph = iD.actions.Circularize('-', projection, 3)(graph);
expect(graph.entity('a')).to.be.undefined;
expect(graph.hasEntity('a')).to.be.undefined;
});
it("reconnects unused nodes that are members of other ways", function () {
@@ -56,7 +56,7 @@ describe("iD.actions.Circularize", function () {
graph = iD.actions.Circularize('-', projection, 3)(graph);
expect(graph.entity('a')).to.be.undefined;
expect(graph.hasEntity('a')).to.be.undefined;
expect(graph.entity('=').nodes).to.eql(['c']);
});

View File

@@ -8,8 +8,8 @@ describe("iD.actions.Connect", function() {
graph = iD.actions.Connect(['a', 'b', 'c'])(graph);
expect(graph.entity('a')).to.be.undefined;
expect(graph.entity('b')).to.be.undefined;
expect(graph.hasEntity('a')).to.be.undefined;
expect(graph.hasEntity('b')).to.be.undefined;
expect(graph.entity('c')).not.to.be.undefined;
});
@@ -87,7 +87,7 @@ describe("iD.actions.Connect", function() {
graph = iD.actions.Connect(['b', 'c'])(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'c']);
expect(graph.entity('b')).to.be.undefined;
expect(graph.hasEntity('b')).to.be.undefined;
});
it("merges adjacent nodes with connections", function() {
@@ -117,7 +117,7 @@ describe("iD.actions.Connect", function() {
expect(graph.entity('-').nodes).to.eql(['a', 'c']);
expect(graph.entity('|').nodes).to.eql(['c', 'd']);
expect(graph.entity('b')).to.be.undefined;
expect(graph.hasEntity('b')).to.be.undefined;
});
it("deletes a degenerate way", function() {
@@ -133,8 +133,8 @@ describe("iD.actions.Connect", function() {
graph = iD.actions.Connect(['a', 'b'])(graph);
expect(graph.entity('a')).to.be.undefined;
expect(graph.entity('-')).to.be.undefined;
expect(graph.hasEntity('a')).to.be.undefined;
expect(graph.hasEntity('-')).to.be.undefined;
});
it("merges tags to the surviving node", function() {

View File

@@ -5,9 +5,9 @@ describe("iD.actions.DeleteMultiple", function () {
r = iD.Relation(),
action = iD.actions.DeleteMultiple([n.id, w.id, r.id]),
graph = action(iD.Graph([n, w, r]));
expect(graph.entity(n.id)).to.be.undefined;
expect(graph.entity(w.id)).to.be.undefined;
expect(graph.entity(r.id)).to.be.undefined;
expect(graph.hasEntity(n.id)).to.be.undefined;
expect(graph.hasEntity(w.id)).to.be.undefined;
expect(graph.hasEntity(r.id)).to.be.undefined;
});
it("deletes a way and one of its nodes", function () {
@@ -15,7 +15,7 @@ describe("iD.actions.DeleteMultiple", function () {
w = iD.Way({nodes: [n.id]}),
action = iD.actions.DeleteMultiple([w.id, n.id]),
graph = action(iD.Graph([n, w]));
expect(graph.entity(w.id)).to.be.undefined;
expect(graph.entity(n.id)).to.be.undefined;
expect(graph.hasEntity(w.id)).to.be.undefined;
expect(graph.hasEntity(n.id)).to.be.undefined;
});
});

View File

@@ -3,7 +3,7 @@ describe("iD.actions.DeleteNode", function () {
var node = iD.Node(),
action = iD.actions.DeleteNode(node.id),
graph = action(iD.Graph([node]));
expect(graph.entity(node.id)).to.be.undefined;
expect(graph.hasEntity(node.id)).to.be.undefined;
});
it("removes the node from parent ways", function () {
@@ -31,7 +31,7 @@ describe("iD.actions.DeleteNode", function () {
way = iD.Way({nodes: [node1.id, node2.id]}),
action = iD.actions.DeleteNode(node1.id),
graph = action(iD.Graph([node1, node2, way]));
expect(graph.entity(way.id)).to.be.undefined;
expect(graph.hasEntity(way.id)).to.be.undefined;
});
it("deletes degenerate circular ways", function () {
@@ -40,6 +40,6 @@ describe("iD.actions.DeleteNode", function () {
way = iD.Way({nodes: [node1.id, node2.id, node1.id]}),
action = iD.actions.DeleteNode(node2.id),
graph = action(iD.Graph([node1, node2, way]));
expect(graph.entity(way.id)).to.be.undefined;
expect(graph.hasEntity(way.id)).to.be.undefined;
});
});

View File

@@ -3,7 +3,7 @@ describe("iD.actions.DeleteRelation", function () {
var relation = iD.Relation(),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([relation]));
expect(graph.entity(relation.id)).to.be.undefined;
expect(graph.hasEntity(relation.id)).to.be.undefined;
});
it("removes the relation from parent relations", function () {
@@ -20,7 +20,7 @@ describe("iD.actions.DeleteRelation", function () {
relation = iD.Relation({members: [{id: node.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([node, relation]));
expect(graph.entity(node.id)).to.be.undefined;
expect(graph.hasEntity(node.id)).to.be.undefined;
});
it("does not delete member nodes referenced by another parent", function() {
@@ -29,7 +29,7 @@ describe("iD.actions.DeleteRelation", function () {
relation = iD.Relation({members: [{id: node.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([node, way, relation]));
expect(graph.entity(node.id)).not.to.be.undefined;
expect(graph.hasEntity(node.id)).not.to.be.undefined;
});
it("does not delete member nodes with interesting tags", function() {
@@ -37,7 +37,7 @@ describe("iD.actions.DeleteRelation", function () {
relation = iD.Relation({members: [{id: node.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([node, relation]));
expect(graph.entity(node.id)).not.to.be.undefined;
expect(graph.hasEntity(node.id)).not.to.be.undefined;
});
it("deletes member ways not referenced by another parent", function() {
@@ -45,7 +45,7 @@ describe("iD.actions.DeleteRelation", function () {
relation = iD.Relation({members: [{id: way.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([way, relation]));
expect(graph.entity(way.id)).to.be.undefined;
expect(graph.hasEntity(way.id)).to.be.undefined;
});
it("does not delete member ways referenced by another parent", function() {
@@ -54,7 +54,7 @@ describe("iD.actions.DeleteRelation", function () {
relation2 = iD.Relation({members: [{id: way.id}]}),
action = iD.actions.DeleteRelation(relation1.id),
graph = action(iD.Graph([way, relation1, relation2]));
expect(graph.entity(way.id)).not.to.be.undefined;
expect(graph.hasEntity(way.id)).not.to.be.undefined;
});
it("does not delete member ways with interesting tags", function() {
@@ -62,7 +62,7 @@ describe("iD.actions.DeleteRelation", function () {
relation = iD.Relation({members: [{id: way.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([way, relation]));
expect(graph.entity(way.id)).not.to.be.undefined;
expect(graph.hasEntity(way.id)).not.to.be.undefined;
});
it("deletes nodes of deleted member ways", function() {
@@ -71,6 +71,6 @@ describe("iD.actions.DeleteRelation", function () {
relation = iD.Relation({members: [{id: way.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([node, way, relation]));
expect(graph.entity(node.id)).to.be.undefined;
expect(graph.hasEntity(node.id)).to.be.undefined;
});
});

View File

@@ -3,7 +3,7 @@ describe("iD.actions.DeleteWay", function() {
var way = iD.Way(),
action = iD.actions.DeleteWay(way.id),
graph = iD.Graph([way]).update(action);
expect(graph.entity(way.id)).to.be.undefined;
expect(graph.hasEntity(way.id)).to.be.undefined;
});
it("removes a way from parent relations", function() {
@@ -19,7 +19,7 @@ describe("iD.actions.DeleteWay", function() {
way = iD.Way({nodes: [node.id]}),
action = iD.actions.DeleteWay(way.id),
graph = iD.Graph([node, way]).update(action);
expect(graph.entity(node.id)).to.be.undefined;
expect(graph.hasEntity(node.id)).to.be.undefined;
});
it("does not delete member nodes referenced by another parent", function() {
@@ -28,7 +28,7 @@ describe("iD.actions.DeleteWay", function() {
way2 = iD.Way({nodes: [node.id]}),
action = iD.actions.DeleteWay(way1.id),
graph = iD.Graph([node, way1, way2]).update(action);
expect(graph.entity(node.id)).not.to.be.undefined;
expect(graph.hasEntity(node.id)).not.to.be.undefined;
});
it("deletes multiple member nodes", function() {
@@ -37,8 +37,8 @@ describe("iD.actions.DeleteWay", function() {
way = iD.Way({nodes: [a.id, b.id]}),
action = iD.actions.DeleteWay(way.id),
graph = iD.Graph([a, b, way]).update(action);
expect(graph.entity(a.id)).to.be.undefined;
expect(graph.entity(b.id)).to.be.undefined;
expect(graph.hasEntity(a.id)).to.be.undefined;
expect(graph.hasEntity(b.id)).to.be.undefined;
});
it("deletes a circular way's start/end node", function() {
@@ -48,9 +48,9 @@ describe("iD.actions.DeleteWay", function() {
way = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
action = iD.actions.DeleteWay(way.id),
graph = iD.Graph([a, b, c, way]).update(action);
expect(graph.entity(a.id)).to.be.undefined;
expect(graph.entity(b.id)).to.be.undefined;
expect(graph.entity(c.id)).to.be.undefined;
expect(graph.hasEntity(a.id)).to.be.undefined;
expect(graph.hasEntity(b.id)).to.be.undefined;
expect(graph.hasEntity(c.id)).to.be.undefined;
});
it("does not delete member nodes with interesting tags", function() {
@@ -58,6 +58,6 @@ describe("iD.actions.DeleteWay", function() {
way = iD.Way({nodes: [node.id]}),
action = iD.actions.DeleteWay(way.id),
graph = iD.Graph([node, way]).update(action);
expect(graph.entity(node.id)).not.to.be.undefined;
expect(graph.hasEntity(node.id)).not.to.be.undefined;
});
});

View File

@@ -91,7 +91,7 @@ describe("iD.actions.Join", function () {
graph = iD.actions.Join(['-', '='])(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.entity('=')).to.be.undefined;
expect(graph.hasEntity('=')).to.be.undefined;
});
it("joins a <-- b <== c", function () {
@@ -108,7 +108,7 @@ describe("iD.actions.Join", function () {
graph = iD.actions.Join(['-', '='])(graph);
expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']);
expect(graph.entity('=')).to.be.undefined;
expect(graph.hasEntity('=')).to.be.undefined;
});
it("joins a <-- b ==> c", function () {
@@ -126,7 +126,7 @@ describe("iD.actions.Join", function () {
graph = iD.actions.Join(['-', '='])(graph);
expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']);
expect(graph.entity('=')).to.be.undefined;
expect(graph.hasEntity('=')).to.be.undefined;
expect(graph.entity('-').tags).to.eql({'lanes:backward': 2});
});
@@ -145,7 +145,7 @@ describe("iD.actions.Join", function () {
graph = iD.actions.Join(['-', '='])(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.entity('=')).to.be.undefined;
expect(graph.hasEntity('=')).to.be.undefined;
expect(graph.entity('-').tags).to.eql({'lanes:backward': 2});
});

View File

@@ -12,8 +12,8 @@ describe("iD.actions.Merge", function () {
graph = action(graph);
expect(graph.entity('a')).to.be.undefined;
expect(graph.entity('b')).to.be.undefined;
expect(graph.hasEntity('a')).to.be.undefined;
expect(graph.hasEntity('b')).to.be.undefined;
expect(graph.entity('w').tags).to.eql({a: 'a', b: 'b'});
expect(graph.entity('r').members).to.eql([{id: 'w', role: 'r', type: 'way'}]);
});
@@ -31,8 +31,8 @@ describe("iD.actions.Merge", function () {
graph = action(graph);
expect(graph.entity('a')).to.be.undefined;
expect(graph.entity('b')).to.be.undefined;
expect(graph.hasEntity('a')).to.be.undefined;
expect(graph.hasEntity('b')).to.be.undefined;
expect(graph.entity('w').tags).to.eql({a: 'a', b: 'b', area: 'yes'});
expect(graph.entity('r').members).to.eql([{id: 'w', role: 'r', type: 'way'}]);
});

View File

@@ -40,6 +40,30 @@ describe('iD.Graph', function() {
});
});
describe("#hasEntity", function () {
it("returns the entity when present", function () {
var node = iD.Node(),
graph = iD.Graph([node]);
expect(graph.hasEntity(node.id)).to.equal(node);
});
it("returns undefined when the entity is not present", function () {
expect(iD.Graph().hasEntity('1')).to.be.undefined;
});
});
describe("#entity", function () {
it("returns the entity when present", function () {
var node = iD.Node(),
graph = iD.Graph([node]);
expect(graph.entity(node.id)).to.equal(node);
});
it("throws when the entity is not present", function () {
expect(function() { iD.Graph().entity('1'); }).to.throw;
});
});
describe("#freeze", function () {
it("sets the frozen flag", function () {
expect(iD.Graph([], true).freeze().frozen).to.be.true;
@@ -209,7 +233,7 @@ describe('iD.Graph', function() {
it("removes the entity from the result", function () {
var node = iD.Node(),
graph = iD.Graph([node]);
expect(graph.remove(node).entity(node.id)).to.be.undefined;
expect(graph.remove(node).hasEntity(node.id)).to.be.undefined;
});
it("removes the entity as a parentWay", function () {
@@ -323,7 +347,7 @@ describe('iD.Graph', function() {
graph.update(function (graph) { graph.remove(node); });
expect(graph.entity(node.id)).to.be.undefined;
expect(graph.hasEntity(node.id)).to.be.undefined;
});
it("executes all of the given functions", function () {
@@ -336,7 +360,7 @@ describe('iD.Graph', function() {
function (graph) { graph.replace(b); }
);
expect(graph.entity(a.id)).to.be.undefined;
expect(graph.hasEntity(a.id)).to.be.undefined;
expect(graph.entity(b.id)).to.equal(b);
});
});

View File

@@ -283,10 +283,10 @@ describe("iD.History", function () {
history.perform(iD.actions.DeleteNode('n2'));
history.save();
history.reset();
expect(history.graph().entity('n')).to.be.undefined
expect(history.graph().hasEntity('n')).to.be.undefined
history.restore();
expect(history.graph().entity('n').id).to.equal('n');
expect(history.graph().entity('n2')).to.be.undefined;
expect(history.graph().hasEntity('n2')).to.be.undefined;
});
});
});