diff --git a/css/map.css b/css/map.css index b2929f149..fc54d7499 100644 --- a/css/map.css +++ b/css/map.css @@ -142,29 +142,37 @@ path.stroke.tag-railway-subway { stroke-dasharray: 8,8; } -path.area { +path.area, +path.multipolygon { stroke-width:2; stroke:#fff; fill:#fff; fill-opacity:0.3; } +path.multipolygon { + fill-rule: evenodd; +} + path.area.selected { stroke-width:4 !important; } -path.area.tag-natural { +path.area.tag-natural, +path.multipolygon.tag-natural { stroke: #ADD6A5; fill: #ADD6A5; stroke-width:1; } -path.area.tag-natural-water { +path.area.tag-natural-water, +path.multipolygon.tag-natural-water { stroke: #6382FF; fill: #ADBEFF; } -path.area.tag-building { +path.area.tag-building, +path.multipolygon.tag-building { stroke: #9E176A; stroke-width: 1; fill: #ff6ec7; @@ -173,7 +181,11 @@ path.area.tag-building { path.area.tag-landuse, path.area.tag-natural-wood, path.area.tag-natural-tree, -path.area.tag-natural-grassland { +path.area.tag-natural-grassland, +path.multipolygon.tag-landuse, +path.multipolygon.tag-natural-wood, +path.multipolygon.tag-natural-tree, +path.multipolygon.tag-natural-grassland { stroke: #006B34; stroke-width: 1; fill: #189E59; diff --git a/index.html b/index.html index 2d42c9f1c..4e42441f7 100644 --- a/index.html +++ b/index.html @@ -41,6 +41,7 @@ + diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 467f7f351..ec232d542 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -21,6 +21,7 @@ iD.Map = function() { vertices = iD.svg.Vertices(roundedProjection), lines = iD.svg.Lines(roundedProjection), areas = iD.svg.Areas(roundedProjection), + multipolygons = iD.svg.Multipolygons(roundedProjection), midpoints = iD.svg.Midpoints(roundedProjection), tail = d3.tail(), surface, tilegroup; @@ -102,6 +103,7 @@ iD.Map = function() { .call(vertices, graph, all, filter) .call(lines, graph, all, filter) .call(areas, graph, all, filter) + .call(multipolygons, graph, all, filter) .call(midpoints, graph, all, filter); } diff --git a/js/id/svg/multipolygons.js b/js/id/svg/multipolygons.js new file mode 100644 index 000000000..bad07ff41 --- /dev/null +++ b/js/id/svg/multipolygons.js @@ -0,0 +1,54 @@ +iD.svg.Multipolygons = function(projection) { + return function(surface, graph, entities, filter) { + var multipolygons = []; + + for (var i = 0; i < entities.length; i++) { + var entity = entities[i]; + if (entity.geometry() === 'relation' && entity.tags.type === 'multipolygon') { + multipolygons.push(entity); + } + } + + var lineStrings = {}; + + function lineString(entity) { + if (lineStrings[entity.id] !== undefined) { + return lineStrings[entity.id]; + } + + var multipolygon = entity.multipolygon(graph); + if (entity.members.length == 0 || !multipolygon) { + return (lineStrings[entity.id] = null); + } + + multipolygon = _.flatten(multipolygon, true); + return (lineStrings[entity.id] = + multipolygon.map(function (ring) { + return 'M' + ring.map(function (node) { return projection(node.loc); }).join('L'); + }).join("")); + } + + function drawPaths(group, multipolygons, filter, classes) { + var paths = group.selectAll('path.multipolygon') + .filter(filter) + .data(multipolygons, iD.Entity.key); + + paths.enter() + .append('path') + .attr('class', classes); + + paths + .order() + .attr('d', lineString) + .call(iD.svg.TagClasses()); + + paths.exit() + .remove(); + + return paths; + } + + var fill = surface.select('.layer-fill'), + paths = drawPaths(fill, multipolygons, filter, 'relation multipolygon'); + }; +}; diff --git a/js/id/svg/tag_classes.js b/js/id/svg/tag_classes.js index 43562fd5c..0295ca7e6 100644 --- a/js/id/svg/tag_classes.js +++ b/js/id/svg/tag_classes.js @@ -1,7 +1,7 @@ iD.svg.TagClasses = function() { var keys = iD.util.trueObj([ 'highway', 'railway', 'motorway', 'amenity', 'natural', - 'landuse', 'building', 'oneway', 'bridge' + 'landuse', 'building', 'oneway', 'bridge', 'boundary' ]), tagClassRe = /^tag-/; return function tagClassesSelection(selection) { diff --git a/test/index.html b/test/index.html index 96095a244..ab7b521cd 100644 --- a/test/index.html +++ b/test/index.html @@ -43,6 +43,7 @@ + @@ -153,6 +154,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index cf333b416..9044adf66 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -62,6 +62,7 @@ + diff --git a/test/spec/svg/multipolygons.js b/test/spec/svg/multipolygons.js new file mode 100644 index 000000000..67f44ebae --- /dev/null +++ b/test/spec/svg/multipolygons.js @@ -0,0 +1,43 @@ +describe("iD.svg.Multipolygons", function () { + var surface, + projection = Object, + filter = d3.functor(true); + + beforeEach(function () { + surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) + .call(iD.svg.Surface()); + }); + + it("adds relation and multipolygon classes", function () { + var relation = iD.Relation({tags: {type: 'multipolygon'}}), + graph = iD.Graph([relation]); + + surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter); + + expect(surface.select('path')).to.be.classed('relation'); + expect(surface.select('path')).to.be.classed('multipolygon'); + }); + + it("adds tag classes", function () { + var relation = iD.Relation({tags: {type: 'multipolygon', boundary: "administrative"}}), + graph = iD.Graph([relation]); + + surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter); + + expect(surface.select('.relation')).to.be.classed('tag-boundary'); + expect(surface.select('.relation')).to.be.classed('tag-boundary-administrative'); + }); + + it("preserves non-multipolygon paths", function () { + var relation = iD.Relation({tags: {type: 'multipolygon'}}), + graph = iD.Graph([relation]); + + surface.select('.layer-fill') + .append('path') + .attr('class', 'other'); + + surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter); + + expect(surface.selectAll('.other')[0].length).to.equal(1); + }); +});