Replace Graph#fetch with Graph#childNodes

Having two kinds of Ways (fetched and non-fetched)
introduced some accidental complexity. This brings things
more in line with how parentWays/parentRelations work.

Fixes #73.
This commit is contained in:
John Firebaugh
2013-01-25 15:08:09 -05:00
parent 74d006ea08
commit 5ea855e18d
19 changed files with 75 additions and 73 deletions
+10 -9
View File
@@ -1,10 +1,11 @@
iD.actions.Circular = function(wayId, map) {
var action = function(graph) {
var way = graph.fetch(wayId),
var way = graph.entity(wayId),
nodes = graph.childNodes(way),
tags = {}, key, role;
var points = way.nodes.map(function(n) {
var points = nodes.map(function(n) {
return map.projection(n.loc);
}),
centroid = d3.geom.polygon(points).centroid(),
@@ -22,21 +23,21 @@ iD.actions.Circular = function(wayId, map) {
circular_nodes.push(circular_nodes[0]);
for (i = 0; i < way.nodes.length; i++) {
if (graph.parentWays(way.nodes[i]).length > 1) {
for (i = 0; i < nodes.length; i++) {
if (graph.parentWays(nodes[i]).length > 1) {
var closest, closest_dist = Infinity, dist;
for (var j = 0; j < circular_nodes.length; j++) {
dist = iD.geo.dist(circular_nodes[j].loc, way.nodes[i].loc);
dist = iD.geo.dist(circular_nodes[j].loc, nodes[i].loc);
if (dist < closest_dist) {
closest_dist = dist;
closest = j;
}
}
circular_nodes.splice(closest, 1, way.nodes[i]);
if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, way.nodes[i]);
else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, way.nodes[i]);
circular_nodes.splice(closest, 1, nodes[i]);
if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]);
else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]);
} else {
graph = graph.remove(way.nodes[i]);
graph = graph.remove(nodes[i]);
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ iD.behavior.DragWay = function(mode) {
return d && d.id === mode.entity.id;
})
.origin(function(entity) {
return projection(entity.nodes[0].loc);
return projection(history.graph().entity(entity.nodes[0]).loc);
})
.on('start', function() {
history.perform(
+4 -6
View File
@@ -1,7 +1,7 @@
iD.format.GeoJSON = {
mapping: function(entity) {
mapping: function(entity, graph) {
if (this.mappings[entity.type]) {
return this.mappings[entity.type](entity);
return this.mappings[entity.type](entity, graph);
}
},
mappings: {
@@ -15,15 +15,13 @@ iD.format.GeoJSON = {
}
};
},
way: function(entity) {
way: function(entity, graph) {
return {
type: 'Feature',
properties: entity.tags,
geometry: {
'type': 'LineString',
'coordinates': entity.nodes.map(function(node) {
return node.loc;
})
'coordinates': _.pluck(graph.childNodes(entity), 'loc')
}
};
}
+2 -2
View File
@@ -86,8 +86,8 @@ iD.format.XML = {
var r = {
way: {
'@id': entity.id.replace('w', ''),
nd: entity.nodes.map(function(e) {
return { keyAttributes: { ref: e.id.replace('n', '') } };
nd: entity.nodes.map(function(id) {
return { keyAttributes: { ref: id.replace('n', '') } };
}),
'@version': (entity.version || 0),
tag: _.map(entity.tags, function(v, k) {
+4 -4
View File
@@ -16,9 +16,9 @@ iD.geo.dist = function(a, b) {
iD.geo.chooseIndex = function(way, point, map) {
var dist = iD.geo.dist,
projNodes = way.nodes.map(function(n) {
return map.projection(n.loc);
});
graph = map.history().graph(),
nodes = graph.childNodes(way),
projNodes = nodes.map(function(n) { return map.projection(n.loc); });
for (var i = 0, changes = []; i < projNodes.length - 1; i++) {
changes[i] =
@@ -28,7 +28,7 @@ iD.geo.chooseIndex = function(way, point, map) {
var idx = _.indexOf(changes, _.min(changes)),
ratio = dist(projNodes[idx], point) / dist(projNodes[idx], projNodes[idx + 1]),
loc = iD.geo.interp(way.nodes[idx].loc, way.nodes[idx + 1].loc, ratio);
loc = iD.geo.interp(nodes[idx].loc, nodes[idx + 1].loc, ratio);
return {
index: idx + 1,
+14 -13
View File
@@ -13,7 +13,7 @@ iD.Graph = function(entities, mutable) {
this.transients = {};
this._parentWays = {};
this._parentRels = {};
this._fetches = {};
this._childNodes = {};
if (!mutable) {
this.freeze();
@@ -88,6 +88,18 @@ iD.Graph.prototype = {
return this._parentRels[entity.id] || [];
},
childNodes: function(entity) {
if (this._childNodes[entity.id])
return this._childNodes[entity.id];
var nodes = [];
for (var i = 0, l = entity.nodes.length; i < l; i++) {
nodes[i] = this.entity(entity.nodes[i]);
}
return (this._childNodes[entity.id] = nodes);
},
merge: function(graph) {
for (var i in graph.entities) {
this.original[i] = graph.entities[i];
@@ -138,23 +150,12 @@ iD.Graph.prototype = {
for (var i in this.entities) {
var entity = this.entities[i];
if (entity && entity.intersects(extent, this)) {
items.push(this.fetch(entity.id));
items.push(entity);
}
}
return items;
},
// Resolve the id references in a way, replacing them with actual objects.
fetch: function(id) {
if (this._fetches[id]) return this._fetches[id];
var entity = this.entities[id], nodes = [];
if (!entity || !entity.nodes || !entity.nodes.length) return entity;
for (var i = 0, l = entity.nodes.length; i < l; i++) {
nodes[i] = this.fetch(entity.nodes[i]);
}
return (this._fetches[id] = iD.Entity(entity, {nodes: nodes}));
},
difference: function (graph) {
var result = [],
keys = Object.keys(this.entities),
+3 -3
View File
@@ -99,13 +99,13 @@ iD.History = function() {
return {
modified: current.modified().map(function (id) {
return current.fetch(id);
return current.entity(id);
}),
created: current.created().map(function (id) {
return current.fetch(id);
return current.entity(id);
}),
deleted: current.deleted().map(function (id) {
return initial.fetch(id);
return initial.entity(id);
})
};
},
+1 -1
View File
@@ -82,7 +82,7 @@ _.extend(iD.Relation.prototype, {
multipolygon: function(resolver) {
var members = this.members
.filter(function (m) { return m.type === 'way' && resolver.entity(m.id); })
.map(function (m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.fetch(m.id).nodes }; });
.map(function (m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.childNodes(resolver.entity(m.id)) }; });
function join(ways) {
var joined = [], current, first, last, i, how, what;
+3 -7
View File
@@ -14,13 +14,9 @@ _.extend(iD.Way.prototype, {
extent: function(resolver) {
return resolver.transient(this, 'extent', function() {
var extent = iD.geo.Extent();
for (var i = 0, l = this.nodes.length; i < l; i++) {
var node = this.nodes[i];
if (node.loc === undefined) node = resolver.entity(node);
extent = extent.extend(node.loc);
}
return extent;
return this.nodes.reduce(function (extent, id) {
return extent.extend(resolver.entity(id).extent(resolver));
}, iD.geo.Extent());
});
},
+2 -2
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.fetch(parent.id);
only[parent.id] = graph.entity(parent.id);
addParents(graph.parentRelations(parent));
}
}
@@ -78,7 +78,7 @@ iD.Map = function() {
for (var j = 0; j < difference.length; j++) {
var id = difference[j],
entity = graph.fetch(id);
entity = graph.entity(id);
// Even if the entity is false (deleted), it needs to be
// removed from the surface
+2 -2
View File
@@ -11,7 +11,7 @@ iD.svg = {
};
},
LineString: function (projection) {
LineString: function (projection, graph) {
var cache = {};
return function (entity) {
if (cache[entity.id] !== undefined) {
@@ -23,7 +23,7 @@ iD.svg = {
}
return (cache[entity.id] =
'M' + entity.nodes.map(function (n) { return projection(n.loc); }).join('L'));
'M' + graph.childNodes(entity).map(function (n) { return projection(n.loc); }).join('L'));
}
}
};
+1 -1
View File
@@ -38,7 +38,7 @@ iD.svg.Areas = function(projection) {
areas.sort(areastack);
var lineString = iD.svg.LineString(projection);
var lineString = iD.svg.LineString(projection, graph);
function drawPaths(group, areas, filter, classes) {
var paths = group.selectAll('path.area')
+2 -2
View File
@@ -257,7 +257,7 @@ iD.svg.Labels = function(projection) {
}
function getLineLabel(entity, width, height) {
var nodes = _.pluck(entity.nodes, 'loc').map(projection),
var nodes = _.pluck(graph.childNodes(entity), 'loc').map(projection),
length = iD.geo.pathLength(nodes);
if (length < width + 20) return;
@@ -285,7 +285,7 @@ iD.svg.Labels = function(projection) {
}
function getAreaLabel(entity, width, height) {
var nodes = _.pluck(entity.nodes, 'loc')
var nodes = _.pluck(graph.childNodes(entity), 'loc')
.map(iD.svg.RoundProjection(projection)),
centroid = d3.geom.polygon(nodes).centroid(),
extent = entity.extent(graph),
+1 -1
View File
@@ -73,7 +73,7 @@ iD.svg.Lines = function(projection) {
lines.sort(waystack);
var lineString = iD.svg.LineString(projection);
var lineString = iD.svg.LineString(projection, graph);
var shadow = surface.select('.layer-shadow'),
casing = surface.select('.layer-casing'),
+5 -4
View File
@@ -8,12 +8,13 @@ iD.svg.Midpoints = function(projection) {
if (entity.type !== 'way')
continue;
for (var j = 0; j < entity.nodes.length - 1; j++) {
var a = projection(entity.nodes[j].loc);
var b = projection(entity.nodes[j + 1].loc);
var nodes = graph.childNodes(entity);
for (var j = 0; j < nodes.length - 1; j++) {
var a = projection(nodes[j].loc);
var b = projection(nodes[j + 1].loc);
if (iD.geo.dist(a, b) > 40) {
midpoints.push({
loc: iD.geo.interp(entity.nodes[j].loc, entity.nodes[j + 1].loc, 0.5),
loc: iD.geo.interp(nodes[j].loc, nodes[j + 1].loc, 0.5),
way: entity.id,
index: j + 1,
midpoint: true
+1 -1
View File
@@ -90,7 +90,7 @@
filter = d3.functor(true),
a = iD.Node({loc: [15, 15]}),
b = iD.Node({loc: [185, 15]}),
way = iD.Way({nodes: [a, b]}),
way = iD.Way({nodes: [a.id, b.id]}),
vertices = iD.svg.Vertices(projection),
lines = iD.svg.Lines(projection),
midpoints = iD.svg.Midpoints(projection);
+11 -8
View File
@@ -1,14 +1,17 @@
describe('iD.format.GeoJSON', function() {
describe('#mapping', function() {
it('should be able to map a node to geojson', function() {
expect(iD.format.GeoJSON.mapping({ type: 'node', loc: [-77, 38] }).geometry.type).to.equal('Point');
it('converts a node to GeoJSON', function() {
var node = iD.Node({loc: [-77, 38]}),
graph = iD.Graph([node]);
expect(iD.format.GeoJSON.mapping(node, graph).geometry.type).to.equal('Point');
});
it('should be able to map a way to geojson', function() {
var way = { type: 'way', nodes: [] };
var gj = iD.format.GeoJSON.mapping(way);
expect(gj.type).to.equal('Feature');
expect(gj.geometry.type).to.equal('LineString');
it('converts a way to GeoJSON', function() {
var way = iD.Way(),
graph = iD.Graph([way]),
json = iD.format.GeoJSON.mapping(way, graph);
expect(json.type).to.equal('Feature');
expect(json.geometry.type).to.equal('LineString');
});
});
});
+3 -3
View File
@@ -129,12 +129,12 @@ describe('iD.Graph', function() {
});
});
describe("#fetch", function () {
it("replaces node ids with references", function () {
describe("#childNodes", function () {
it("returns an array of child nodes", function () {
var node = iD.Node({id: "n1"}),
way = iD.Way({id: "w1", nodes: ["n1"]}),
graph = iD.Graph({n1: node, w1: way});
expect(graph.fetch("w1").nodes).to.eql([node]);
expect(graph.childNodes(way)).to.eql([node]);
});
});
+5 -3
View File
@@ -2,16 +2,18 @@ describe("iD.svg.LineString", function () {
it("returns an SVG path description for the entity's nodes", function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [2, 3]}),
way = iD.Way({nodes: [a, b]}),
way = iD.Way({nodes: [a.id, b.id]}),
graph = iD.Graph([a, b, way]),
projection = Object;
expect(iD.svg.LineString(projection)(way)).to.equal("M0,0L2,3");
expect(iD.svg.LineString(projection, graph)(way)).to.equal("M0,0L2,3");
});
it("returns null for an entity with no nodes", function () {
var way = iD.Way(),
graph = iD.Graph([way]),
projection = Object;
expect(iD.svg.LineString(projection)(way)).to.be.null;
expect(iD.svg.LineString(projection, graph)(way)).to.be.null;
});
});