From 9495f8f1c02bcda1c061173905c0263be9e48345 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sun, 5 May 2013 10:38:05 -0700 Subject: [PATCH] Optimize area rendering Use iD.svg.Path for caching, and teach iD.svg.Path to round coordinates. --- js/id/svg.js | 23 +++++++++++++++++++++-- js/id/svg/areas.js | 23 +++++++++++------------ test/spec/svg/areas.js | 32 +++++++++++++++++++++----------- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/js/id/svg.js b/js/id/svg.js index cbf226e63..013d5cf98 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -17,10 +17,29 @@ iD.svg = { var cache = {}, path = d3.geo.path().projection(projection); - return function(entity) { + function result(entity) { if (entity.id in cache) return cache[entity.id]; - return cache[entity.id] = path(entity.asGeoJSON(graph)); + + var buffer = ''; + + path.context({ + beginPath: function() {}, + moveTo: function(x, y) { buffer += 'M' + Math.floor(x) + ',' + Math.floor(y); }, + lineTo: function(x, y) { buffer += 'L' + Math.floor(x) + ',' + Math.floor(y); }, + arc: function() {}, + closePath: function() { buffer += 'Z'; } + }); + + path(entity.asGeoJSON(graph)); + + return cache[entity.id] = buffer; + } + + result.area = function(entity) { + return path.area(entity.asGeoJSON(graph, true)); }; + + return result; }, OneWaySegments: function(projection, graph, dt) { diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js index 5dc9e0fac..8fbb07f34 100644 --- a/js/id/svg/areas.js +++ b/js/id/svg/areas.js @@ -28,7 +28,7 @@ iD.svg.Areas = function(projection) { } return function drawAreas(surface, graph, entities, filter) { - var path = d3.geo.path().projection(projection), + var path = iD.svg.Path(projection, graph), areas = {}, multipolygon; @@ -39,25 +39,25 @@ iD.svg.Areas = function(projection) { if (multipolygon = iD.geo.isSimpleMultipolygonOuterMember(entity, graph)) { areas[multipolygon.id] = { entity: multipolygon.mergeTags(entity.tags), - area: Math.abs(path.area(entity.asGeoJSON(graph, true))) + area: Math.abs(path.area(entity)) }; } else if (!areas[entity.id]) { areas[entity.id] = { entity: entity, - area: Math.abs(path.area(entity.asGeoJSON(graph, true))) + area: Math.abs(path.area(entity)) }; } } - areas = d3.values(areas); - areas.sort(function(a, b) { return b.area - a.area; }); + areas = d3.values(areas).filter(function hasPath(a) { return path(a.entity); }); + areas.sort(function areaSort(a, b) { return b.area - a.area; }); areas = _.pluck(areas, 'entity'); - var strokes = areas.filter(function(area) { + var strokes = areas.filter(function isWay(area) { return area.type === 'way'; }); - function drawPaths(areas, klass, closeWay) { + function drawPaths(areas, klass, path) { var paths = surface.select('.layer-' + klass) .selectAll('path.area') .filter(filter) @@ -78,7 +78,7 @@ iD.svg.Areas = function(projection) { paths .order() - .attr('d', function(entity) { return path(entity.asGeoJSON(graph, closeWay)); }); + .attr('d', path); if (klass === 'fill') paths.call(setPattern); @@ -88,9 +88,8 @@ iD.svg.Areas = function(projection) { return paths; } - drawPaths(strokes, 'shadow'); - drawPaths(strokes, 'stroke'); - - drawPaths(areas, 'fill', true); + drawPaths(strokes, 'shadow', path); + drawPaths(strokes, 'stroke', path); + drawPaths(areas, 'fill', path); }; }; diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js index 0bfabaa86..373bb8236 100644 --- a/test/spec/svg/areas.js +++ b/test/spec/svg/areas.js @@ -9,20 +9,30 @@ describe("iD.svg.Areas", function () { }); it("adds way and area classes", function () { - var area = iD.Way({tags: {area: 'yes'}}), - graph = iD.Graph([area]); + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [1, 0]}), + 'c': iD.Node({id: 'c', loc: [1, 1]}), + 'd': iD.Node({id: 'd', loc: [0, 1]}), + 'w': iD.Way({id: 'w', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'a']}) + }); - surface.call(iD.svg.Areas(projection), graph, [area], filter); + surface.call(iD.svg.Areas(projection), graph, [graph.entity('w')], filter); expect(surface.select('path.way')).to.be.classed('way'); expect(surface.select('path.area')).to.be.classed('area'); }); it("adds tag classes", function () { - var area = iD.Way({tags: {area: 'yes', building: 'yes'}}), - graph = iD.Graph([area]); + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [1, 0]}), + 'c': iD.Node({id: 'c', loc: [1, 1]}), + 'd': iD.Node({id: 'd', loc: [0, 1]}), + 'w': iD.Way({id: 'w', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'a']}) + }); - surface.call(iD.svg.Areas(projection), graph, [area], filter); + surface.call(iD.svg.Areas(projection), graph, [graph.entity('w')], filter); expect(surface.select('.area')).to.be.classed('tag-building'); expect(surface.select('.area')).to.be.classed('tag-building-yes'); @@ -47,8 +57,8 @@ describe("iD.svg.Areas", function () { 'b': iD.Node({id: 'b', loc: [1, 0]}), 'c': iD.Node({id: 'c', loc: [1, 1]}), 'd': iD.Node({id: 'd', loc: [0, 1]}), - 's': iD.Way({area: true, tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'a']}), - 'l': iD.Way({area: true, tags: {landuse: 'park'}, nodes: ['a', 'b', 'c', 'd', 'a']}) + 's': iD.Way({id: 's', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'a']}), + 'l': iD.Way({id: 'l', tags: {landuse: 'park'}, nodes: ['a', 'b', 'c', 'd', 'a']}) }), areas = [graph.entity('s'), graph.entity('l')]; @@ -91,7 +101,7 @@ describe("iD.svg.Areas", function () { b = iD.Node({loc: [2, 2]}), c = iD.Node({loc: [3, 3]}), w = iD.Way({tags: {area: 'yes'}, nodes: [a.id, b.id, c.id, a.id]}), - r = iD.Relation({members: [{id: w.id}], tags: {type: 'multipolygon', natural: 'wood'}}), + r = iD.Relation({members: [{id: w.id, type: 'way'}], tags: {type: 'multipolygon', natural: 'wood'}}), graph = iD.Graph([a, b, c, w, r]); surface.call(iD.svg.Areas(projection), graph, [w], filter); @@ -105,7 +115,7 @@ describe("iD.svg.Areas", function () { b = iD.Node({loc: [2, 2]}), c = iD.Node({loc: [3, 3]}), w = iD.Way({tags: {natural: 'wood'}, nodes: [a.id, b.id, c.id, a.id]}), - r = iD.Relation({members: [{id: w.id}], tags: {type: 'multipolygon'}}), + r = iD.Relation({members: [{id: w.id, type: 'way'}], tags: {type: 'multipolygon'}}), graph = iD.Graph([a, b, c, w, r]); surface.call(iD.svg.Areas(projection), graph, [w, r], filter); @@ -120,7 +130,7 @@ describe("iD.svg.Areas", function () { b = iD.Node({loc: [2, 2]}), c = iD.Node({loc: [3, 3]}), w = iD.Way({tags: {natural: 'wood'}, nodes: [a.id, b.id, c.id, a.id]}), - r = iD.Relation({members: [{id: w.id}], tags: {type: 'multipolygon'}}), + r = iD.Relation({members: [{id: w.id, type: 'way'}], tags: {type: 'multipolygon'}}), graph = iD.Graph([a, b, c, w, r]); surface.call(iD.svg.Areas(projection), graph, [w, r], filter);