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 9edb347e4..ec232d542 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -5,6 +5,7 @@ iD.Map = function() {
translateStart,
keybinding = d3.keybinding(),
projection = d3.geo.mercator().scale(1024),
+ roundedProjection = iD.svg.RoundProjection(projection),
zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
@@ -16,11 +17,12 @@ iD.Map = function() {
background = iD.Background()
.projection(projection),
transformProp = iD.util.prefixCSSProperty('Transform'),
- points = iD.svg.Points(),
- vertices = iD.svg.Vertices(),
- lines = iD.svg.Lines(),
- areas = iD.svg.Areas(),
- midpoints = iD.svg.Midpoints(),
+ points = iD.svg.Points(roundedProjection),
+ 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;
@@ -97,11 +99,12 @@ iD.Map = function() {
}
surface
- .call(points, graph, all, filter, projection)
- .call(vertices, graph, all, filter, projection)
- .call(lines, graph, all, filter, projection)
- .call(areas, graph, all, filter, projection)
- .call(midpoints, graph, all, filter, projection);
+ .call(points, graph, all, filter)
+ .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);
}
function editOff() {
diff --git a/js/id/svg.js b/js/id/svg.js
index 5860126e9..e2b1dc2ad 100644
--- a/js/id/svg.js
+++ b/js/id/svg.js
@@ -6,9 +6,24 @@ iD.svg = {
},
PointTransform: function (projection) {
- projection = iD.svg.RoundProjection(projection);
return function (entity) {
return 'translate(' + projection(entity.loc) + ')';
};
+ },
+
+ LineString: function (projection) {
+ var cache = {};
+ return function (entity) {
+ if (cache[entity.id] !== undefined) {
+ return cache[entity.id];
+ }
+
+ if (entity.nodes.length === 0) {
+ return (cache[entity.id] = null);
+ }
+
+ return (cache[entity.id] =
+ 'M' + entity.nodes.map(function (n) { return projection(n.loc); }).join('L'));
+ }
}
};
diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js
index ff24afecc..e77fe8a4d 100644
--- a/js/id/svg/areas.js
+++ b/js/id/svg/areas.js
@@ -1,4 +1,4 @@
-iD.svg.Areas = function() {
+iD.svg.Areas = function(projection) {
var area_stack = {
building: 0,
@@ -26,7 +26,7 @@ iD.svg.Areas = function() {
return as - bs;
}
- return function drawAreas(surface, graph, entities, filter, projection) {
+ return function drawAreas(surface, graph, entities, filter) {
var areas = [];
for (var i = 0; i < entities.length; i++) {
@@ -38,20 +38,10 @@ iD.svg.Areas = function() {
areas.sort(areastack);
- var lineStrings = {};
-
- function lineString(entity) {
- if (lineStrings[entity.id] !== undefined) {
- return lineStrings[entity.id];
- }
- var nodes = _.pluck(entity.nodes, 'loc');
- if (nodes.length === 0) return (lineStrings[entity.id] = '');
- else return (lineStrings[entity.id] =
- 'M' + nodes.map(iD.svg.RoundProjection(projection)).join('L'));
- }
+ var lineString = iD.svg.LineString(projection);
function drawPaths(group, areas, filter, classes) {
- var paths = group.selectAll('path')
+ var paths = group.selectAll('path.area')
.filter(filter)
.data(areas, iD.Entity.key);
diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js
index 3f1b634d7..167a9e75e 100644
--- a/js/id/svg/lines.js
+++ b/js/id/svg/lines.js
@@ -1,4 +1,4 @@
-iD.svg.Lines = function() {
+iD.svg.Lines = function(projection) {
var arrowtext = '►\u3000\u3000',
alength;
@@ -53,7 +53,7 @@ iD.svg.Lines = function() {
return paths;
}
- return function drawLines(surface, graph, entities, filter, projection) {
+ return function drawLines(surface, graph, entities, filter) {
if (!alength) {
var arrow = surface.append('text').text(arrowtext);
@@ -73,15 +73,7 @@ iD.svg.Lines = function() {
lines.sort(waystack);
- function lineString(entity) {
- if (lineStrings[entity.id] !== undefined) {
- return lineStrings[entity.id];
- }
- var nodes = _.pluck(entity.nodes, 'loc');
- if (nodes.length === 0) return (lineStrings[entity.id] = '');
- else return (lineStrings[entity.id] =
- 'M' + nodes.map(iD.svg.RoundProjection(projection)).join('L'));
- }
+ var lineString = iD.svg.LineString(projection);
var casing = surface.select('.layer-casing'),
stroke = surface.select('.layer-stroke'),
diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js
index 9c060ea3b..321e75c8e 100644
--- a/js/id/svg/midpoints.js
+++ b/js/id/svg/midpoints.js
@@ -1,5 +1,5 @@
-iD.svg.Midpoints = function() {
- return function drawMidpoints(surface, graph, entities, filter, projection) {
+iD.svg.Midpoints = function(projection) {
+ return function drawMidpoints(surface, graph, entities, filter) {
var midpoints = [];
for (var i = 0; i < entities.length; i++) {
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/points.js b/js/id/svg/points.js
index 7a2600814..37c4bbf91 100644
--- a/js/id/svg/points.js
+++ b/js/id/svg/points.js
@@ -1,4 +1,4 @@
-iD.svg.Points = function() {
+iD.svg.Points = function(projection) {
function imageHref(d) {
// TODO: optimize
for (var k in d.tags) {
@@ -10,7 +10,7 @@ iD.svg.Points = function() {
return 'icons/unknown.png';
}
- return function drawPoints(surface, graph, entities, filter, projection) {
+ return function drawPoints(surface, graph, entities, filter) {
var points = [];
for (var i = 0; i < entities.length; i++) {
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/js/id/svg/vertices.js b/js/id/svg/vertices.js
index 700ef5538..ff775fa3e 100644
--- a/js/id/svg/vertices.js
+++ b/js/id/svg/vertices.js
@@ -1,5 +1,5 @@
-iD.svg.Vertices = function() {
- return function drawVertices(surface, graph, entities, filter, projection) {
+iD.svg.Vertices = function(projection) {
+ return function drawVertices(surface, graph, entities, filter) {
var vertices = [];
for (var i = 0; i < entities.length; i++) {
diff --git a/test/index.html b/test/index.html
index 195b459f6..ab7b521cd 100644
--- a/test/index.html
+++ b/test/index.html
@@ -43,6 +43,7 @@
+
@@ -151,7 +152,9 @@
+
+
diff --git a/test/index_packaged.html b/test/index_packaged.html
index 92c61df79..9044adf66 100644
--- a/test/index_packaged.html
+++ b/test/index_packaged.html
@@ -60,7 +60,9 @@
+
+
diff --git a/test/spec/svg.js b/test/spec/svg.js
new file mode 100644
index 000000000..265562071
--- /dev/null
+++ b/test/spec/svg.js
@@ -0,0 +1,17 @@
+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]}),
+ projection = Object;
+
+ expect(iD.svg.LineString(projection)(way)).to.equal("M0,0L2,3");
+ });
+
+ it("returns null for an entity with no nodes", function () {
+ var way = iD.Way(),
+ projection = Object;
+
+ expect(iD.svg.LineString(projection)(way)).to.be.null;
+ });
+});
diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js
index 54a7c1d35..112e25d03 100644
--- a/test/spec/svg/areas.js
+++ b/test/spec/svg/areas.js
@@ -1,6 +1,6 @@
describe("iD.svg.Areas", function () {
var surface,
- projection = d3.geo.mercator(),
+ projection = Object,
filter = d3.functor(true);
beforeEach(function () {
@@ -8,13 +8,36 @@ describe("iD.svg.Areas", function () {
.call(iD.svg.Surface());
});
+ it("adds way and area classes", function () {
+ var area = iD.Way({tags: {area: 'yes'}}),
+ graph = iD.Graph([area]);
+
+ surface.call(iD.svg.Areas(projection), graph, [area], filter);
+
+ expect(surface.select('path')).to.be.classed('way');
+ expect(surface.select('path')).to.be.classed('area');
+ });
+
it("adds tag classes", function () {
var area = iD.Way({tags: {area: 'yes', building: 'yes'}}),
graph = iD.Graph([area]);
- surface.call(iD.svg.Areas(), graph, [area], filter, projection);
+ surface.call(iD.svg.Areas(projection), graph, [area], filter);
expect(surface.select('.area')).to.be.classed('tag-building');
expect(surface.select('.area')).to.be.classed('tag-building-yes');
});
+
+ it("preserves non-area paths", function () {
+ var area = iD.Way({tags: {area: 'yes'}}),
+ graph = iD.Graph([area]);
+
+ surface.select('.layer-fill')
+ .append('path')
+ .attr('class', 'other');
+
+ surface.call(iD.svg.Areas(projection), graph, [area], filter);
+
+ expect(surface.selectAll('.other')[0].length).to.equal(1);
+ });
});
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);
+ });
+});
diff --git a/test/spec/svg/points.js b/test/spec/svg/points.js
index 664f88a80..6e991962c 100644
--- a/test/spec/svg/points.js
+++ b/test/spec/svg/points.js
@@ -1,6 +1,6 @@
describe("iD.svg.Points", function () {
var surface,
- projection = d3.geo.mercator(),
+ projection = Object,
filter = d3.functor(true);
beforeEach(function () {
@@ -12,7 +12,7 @@ describe("iD.svg.Points", function () {
var node = iD.Node({tags: {amenity: "cafe"}, loc: [0, 0], _poi: true}),
graph = iD.Graph([node]);
- surface.call(iD.svg.Points(), graph, [node], filter, projection);
+ surface.call(iD.svg.Points(projection), graph, [node], filter);
expect(surface.select('.point')).to.be.classed('tag-amenity');
expect(surface.select('.point')).to.be.classed('tag-amenity-cafe');
diff --git a/test/spec/svg/vertices.js b/test/spec/svg/vertices.js
index 760734053..8ce5e3a80 100644
--- a/test/spec/svg/vertices.js
+++ b/test/spec/svg/vertices.js
@@ -1,6 +1,6 @@
describe("iD.svg.Vertices", function () {
var surface,
- projection = d3.geo.mercator(),
+ projection = Object,
filter = d3.functor(true);
beforeEach(function () {
@@ -12,7 +12,7 @@ describe("iD.svg.Vertices", function () {
var node = iD.Node({tags: {highway: "traffic_signals"}, loc: [0, 0]}),
graph = iD.Graph([node]);
- surface.call(iD.svg.Vertices(), graph, [node], filter, projection);
+ surface.call(iD.svg.Vertices(projection), graph, [node], filter);
expect(surface.select('.vertex')).to.be.classed('tag-highway');
expect(surface.select('.vertex')).to.be.classed('tag-highway-traffic_signals');
@@ -24,7 +24,7 @@ describe("iD.svg.Vertices", function () {
way2 = iD.Way({nodes: [node.id]}),
graph = iD.Graph([node, way1, way2]);
- surface.call(iD.svg.Vertices(), graph, [node], filter, projection);
+ surface.call(iD.svg.Vertices(projection), graph, [node], filter);
expect(surface.select('.vertex')).to.be.classed('shared');
});