diff --git a/js/id/actions/circular.js b/js/id/actions/circular.js index ffa1b8a9e..d6af08560 100644 --- a/js/id/actions/circular.js +++ b/js/id/actions/circular.js @@ -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]); } } diff --git a/js/id/behavior/drag_way.js b/js/id/behavior/drag_way.js index 40709ee07..47045f5ac 100644 --- a/js/id/behavior/drag_way.js +++ b/js/id/behavior/drag_way.js @@ -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( diff --git a/js/id/format/geojson.js b/js/id/format/geojson.js index cf859d9b1..35105b5f6 100644 --- a/js/id/format/geojson.js +++ b/js/id/format/geojson.js @@ -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') } }; } diff --git a/js/id/format/xml.js b/js/id/format/xml.js index a47dfa5df..5334b2d84 100644 --- a/js/id/format/xml.js +++ b/js/id/format/xml.js @@ -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) { diff --git a/js/id/geo.js b/js/id/geo.js index 08df659b0..fcce74b9a 100644 --- a/js/id/geo.js +++ b/js/id/geo.js @@ -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, diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index f9b2c7ab6..43fd0a796 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -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), diff --git a/js/id/graph/history.js b/js/id/graph/history.js index c6636f796..31f32618a 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -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); }) }; }, diff --git a/js/id/graph/relation.js b/js/id/graph/relation.js index 48a86c920..f19684026 100644 --- a/js/id/graph/relation.js +++ b/js/id/graph/relation.js @@ -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; diff --git a/js/id/graph/way.js b/js/id/graph/way.js index 0de0e55a1..19cd0fd7e 100644 --- a/js/id/graph/way.js +++ b/js/id/graph/way.js @@ -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()); }); }, diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index f34ee03cc..e243a01ce 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -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 diff --git a/js/id/svg.js b/js/id/svg.js index a3b174ddb..5b07b3a53 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -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')); } } }; diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js index 0bc8049e9..27edc0243 100644 --- a/js/id/svg/areas.js +++ b/js/id/svg/areas.js @@ -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') diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index d9574d21e..286589010 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -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), diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 21382aefd..65cfccd27 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -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'), diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index a04526f83..76f9c64c5 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -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 diff --git a/test/rendering.html b/test/rendering.html index ebefeaa4a..e5347d0b4 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -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); diff --git a/test/spec/format/geojson.js b/test/spec/format/geojson.js index e17c8573b..4071f051c 100644 --- a/test/spec/format/geojson.js +++ b/test/spec/format/geojson.js @@ -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'); }); }); }); diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 81e76ca6f..25382e7c9 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -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]); }); }); diff --git a/test/spec/svg.js b/test/spec/svg.js index 265562071..4d30e2771 100644 --- a/test/spec/svg.js +++ b/test/spec/svg.js @@ -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; }); });