diff --git a/css/map.css b/css/map.css
index 749aeb7b3..7b35f6bc5 100644
--- a/css/map.css
+++ b/css/map.css
@@ -181,95 +181,72 @@ path.shadow.selected {
}
path.area.stroke,
-path.multipolygon {
+path.line.member-type-multipolygon.stroke {
stroke-width:2;
- stroke:#fff;
}
-path.area.fill,
-path.multipolygon {
- fill:#fff;
- fill-opacity:0.3;
-}
-
-path.multipolygon {
- fill-rule: evenodd;
-}
-
-path.area.fill.member-type-multipolygon {
- fill: none;
-}
-
-path.area.stroke.selected {
+path.area.stroke.selected,
+path.line.member-type-multipolygon.stroke.selected {
stroke-width:4 !important;
}
-path.area.stroke.tag-natural,
-path.multipolygon.tag-natural {
+path.area.stroke {
+ stroke:#fff;
+}
+path.area.fill {
+ fill:#fff;
+ fill-opacity:0.3;
+ fill-rule: evenodd;
+}
+
+path.stroke.tag-natural {
stroke: #b6e199;
stroke-width:1;
}
-path.area.fill.tag-natural,
-path.multipolygon.tag-natural {
+path.fill.tag-natural {
fill: #b6e199;
}
-path.area.stroke.tag-natural-water,
-path.multipolygon.tag-natural-water {
+path.stroke.tag-natural-water {
stroke: #77d3de;
}
-path.area.fill.tag-natural-water,
-path.multipolygon.tag-natural-water {
+path.fill.tag-natural-water {
fill: #77d3de;
}
-path.area.stroke.tag-building,
-path.multipolygon.tag-building {
+path.stroke.tag-building {
stroke: #e06e5f;
stroke-width: 1;
}
-path.area.fill.tag-building,
-path.multipolygon.tag-building {
+path.fill.tag-building {
fill: #e06e5f;
}
-path.area.stroke.tag-landuse,
-path.area.stroke.tag-natural-wood,
-path.area.stroke.tag-natural-tree,
-path.area.stroke.tag-natural-grassland,
-path.area.stroke.tag-leisure-park,
-path.multipolygon.tag-landuse,
-path.multipolygon.tag-natural-wood,
-path.multipolygon.tag-natural-tree,
-path.multipolygon.tag-natural-grassland,
-path.multipolygon.tag-leisure-park {
+path.stroke.tag-landuse,
+path.stroke.tag-natural-wood,
+path.stroke.tag-natural-tree,
+path.stroke.tag-natural-grassland,
+path.stroke.tag-leisure-park {
stroke: #8cd05f;
stroke-width: 1;
}
-path.area.fill.tag-landuse,
-path.area.fill.tag-natural-wood,
-path.area.fill.tag-natural-tree,
-path.area.fill.tag-natural-grassland,
-path.area.fill.tag-leisure-park,
-path.multipolygon.tag-landuse,
-path.multipolygon.tag-natural-wood,
-path.multipolygon.tag-natural-tree,
-path.multipolygon.tag-natural-grassland,
-path.multipolygon.tag-leisure-park {
+path.fill.tag-landuse,
+path.fill.tag-natural-wood,
+path.fill.tag-natural-tree,
+path.fill.tag-natural-grassland,
+path.fill.tag-leisure-park {
fill: #8cd05f;
fill-opacity: 0.2;
}
-path.area.stroke.tag-amenity-parking,
-path.multipolygon.tag-amenity-parking {
+path.stroke.tag-amenity-parking {
stroke: #aaa;
stroke-width: 1;
}
-path.area.fill.tag-amenity-parking,
-path.multipolygon.tag-amenity-parking {
+path.fill.tag-amenity-parking {
fill: #aaa;
}
-path.multipolygon.tag-boundary {
+path.fill.tag-boundary {
fill: none;
}
@@ -526,7 +503,7 @@ path.casing.tag-railway-subway {
/* waterways */
-path.area.fill.tag-waterway {
+path.fill.tag-waterway {
fill: #77d3de;
}
@@ -686,9 +663,7 @@ text.point {
}
.mode-select .area,
-.mode-browse .area,
-.mode-select .multipolygon,
-.mode-browse .multipolygon {
+.mode-browse .area {
cursor: url(../img/cursor-select-area.png), pointer;
}
@@ -701,7 +676,6 @@ text.point {
.vertex:active,
.line:active,
.area:active,
-.multipolygon:active,
.midpoint:active,
.mode-select .selected {
cursor: url(../img/cursor-select-acting.png), pointer;
diff --git a/index.html b/index.html
index afcb8e6be..a58aeb8f2 100644
--- a/index.html
+++ b/index.html
@@ -44,7 +44,6 @@
-
diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js
index 6b648db0e..8c4c4a5cc 100644
--- a/js/id/behavior/draw_way.js
+++ b/js/id/behavior/draw_way.js
@@ -28,7 +28,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
function move(datum) {
var loc = context.map().mouseCoordinates();
- if (datum.type === 'node') {
+ if (datum.id === end.id || datum.id === segment.id) {
+ context.surface().selectAll('.way, .node')
+ .filter(function (d) {
+ return d.id === end.id || d.id === segment.id;
+ })
+ .classed('active', true);
+ } else if (datum.type === 'node') {
loc = datum.loc;
} else if (datum.type === 'way') {
loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc;
diff --git a/js/id/graph/relation.js b/js/id/graph/relation.js
index 1ed9e8ff1..f8540db1f 100644
--- a/js/id/graph/relation.js
+++ b/js/id/graph/relation.js
@@ -26,7 +26,7 @@ _.extend(iD.Relation.prototype, {
},
geometry: function() {
- return 'relation';
+ return this.isMultipolygon() ? 'area' : 'relation';
},
// Return the first member with the given role. A copy of the member object
@@ -83,6 +83,31 @@ _.extend(iD.Relation.prototype, {
return r;
},
+ asGeoJSON: function(resolver) {
+ if (this.isMultipolygon()) {
+ return {
+ type: 'Feature',
+ properties: this.tags,
+ geometry: {
+ type: 'MultiPolygon',
+ coordinates: this.multipolygon(resolver)
+ }
+ };
+ } else {
+ return {
+ type: 'FeatureCollection',
+ properties: this.tags,
+ features: this.members.map(function(member) {
+ return _.extend({role: member.role}, resolver.entity(member.id).asGeoJSON(resolver));
+ })
+ };
+ }
+ },
+
+ isMultipolygon: function() {
+ return this.tags.type === 'multipolygon';
+ },
+
isRestriction: function() {
return !!(this.tags.type && this.tags.type.match(/^restriction:?/));
},
@@ -145,22 +170,20 @@ _.extend(iD.Relation.prototype, {
}
}
- return joined;
+ return joined.map(function (nodes) { return _.pluck(nodes, 'loc'); });
}
function findOuter(inner) {
var o, outer;
- inner = _.pluck(inner, 'loc');
-
for (o = 0; o < outers.length; o++) {
- outer = _.pluck(outers[o], 'loc');
+ outer = outers[o];
if (iD.geo.polygonContainsPolygon(outer, inner))
return o;
}
for (o = 0; o < outers.length; o++) {
- outer = _.pluck(outers[o], 'loc');
+ outer = outers[o];
if (iD.geo.polygonIntersectsPolygon(outer, inner))
return o;
}
diff --git a/js/id/graph/way.js b/js/id/graph/way.js
index aae89a6ce..1830e61a4 100644
--- a/js/id/graph/way.js
+++ b/js/id/graph/way.js
@@ -49,6 +49,7 @@ _.extend(iD.Way.prototype, {
isArea: function() {
return this.tags.area === 'yes' ||
(this.isClosed() &&
+ !_.isEmpty(this.tags) &&
this.tags.area !== 'no' &&
!this.tags.highway &&
!this.tags.barrier);
@@ -103,13 +104,24 @@ _.extend(iD.Way.prototype, {
},
asGeoJSON: function(resolver) {
- return {
- type: 'Feature',
- properties: this.tags,
- geometry: {
- type: 'LineString',
- coordinates: _.pluck(resolver.childNodes(this), 'loc')
- }
- };
+ if (this.isArea()) {
+ return {
+ type: 'Feature',
+ properties: this.tags,
+ geometry: {
+ type: 'Polygon',
+ coordinates: [_.pluck(resolver.childNodes(this), 'loc')]
+ }
+ };
+ } else {
+ return {
+ type: 'Feature',
+ properties: this.tags,
+ geometry: {
+ type: 'LineString',
+ coordinates: _.pluck(resolver.childNodes(this), 'loc')
+ }
+ };
+ }
}
});
diff --git a/js/id/modes/select.js b/js/id/modes/select.js
index 3b0ad82dd..532faaca8 100644
--- a/js/id/modes/select.js
+++ b/js/id/modes/select.js
@@ -143,13 +143,22 @@ iD.modes.Select = function(context, selection, initial) {
}
}
+ function selected(entity) {
+ if (!entity) return false;
+ if (selection.indexOf(entity.id) >= 0) return true;
+ return d3.select(this).classed('stroke') &&
+ _.any(context.graph().parentRelations(entity), function(parent) {
+ return selection.indexOf(parent.id) >= 0;
+ });
+ }
+
d3.select(document)
.call(keybinding);
context.surface()
.on('dblclick.select', dblclick)
.selectAll("*")
- .filter(function(d) { return d && selection.indexOf(d.id) >= 0; })
+ .filter(selected)
.classed('selected', true);
radialMenu = iD.ui.RadialMenu(operations);
diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js
index 19354d7f3..5074abd92 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -19,7 +19,6 @@ iD.Map = function(context) {
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),
labels = iD.svg.Labels(roundedProjection),
tail = d3.tail(),
@@ -87,7 +86,6 @@ iD.Map = function(context) {
.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)
.call(labels, graph, all, filter, dimensions, !difference);
}
diff --git a/js/id/svg.js b/js/id/svg.js
index aadb6b830..3b662b2e7 100644
--- a/js/id/svg.js
+++ b/js/id/svg.js
@@ -27,5 +27,17 @@ iD.svg = {
return projection(n.loc);
}).join('L'));
};
+ },
+
+ MultipolygonMemberTags: function (graph) {
+ return function (entity) {
+ var tags = entity.tags;
+ graph.parentRelations(entity).forEach(function (relation) {
+ if (relation.isMultipolygon()) {
+ tags = _.extend({}, relation.tags, tags);
+ }
+ });
+ return tags;
+ }
}
};
diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js
index 991b5644c..cd9525d4d 100644
--- a/js/id/svg/areas.js
+++ b/js/id/svg/areas.js
@@ -1,38 +1,39 @@
iD.svg.Areas = function(projection) {
return function drawAreas(surface, graph, entities, filter) {
- var areas = [];
+ var path = d3.geo.path().projection(projection),
+ areas = [];
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (entity.geometry(graph) === 'area') {
- var points = graph.childNodes(entity).map(function(n) {
- return projection(n.loc);
- });
-
areas.push({
entity: entity,
- area: entity.isDegenerate() ? 0 : Math.abs(d3.geom.polygon(points).area())
+ area: Math.abs(path.area(entity.asGeoJSON(graph)))
});
}
}
areas.sort(function(a, b) { return b.area - a.area; });
- var lineString = iD.svg.LineString(projection, graph);
+ function drawPaths(group, areas, filter, klass) {
+ var tagClasses = iD.svg.TagClasses();
+
+ if (klass === 'stroke') {
+ tagClasses.tags(iD.svg.MultipolygonMemberTags(graph));
+ }
- function drawPaths(group, areas, filter, classes) {
var paths = group.selectAll('path.area')
.filter(filter)
.data(areas, iD.Entity.key);
paths.enter()
.append('path')
- .attr('class', classes);
+ .attr('class', function (d) { return d.type + ' area ' + klass; });
paths
.order()
- .attr('d', lineString)
- .call(iD.svg.TagClasses())
+ .attr('d', function (entity) { return path(entity.asGeoJSON(graph)); })
+ .call(tagClasses)
.call(iD.svg.MemberClasses(graph));
paths.exit()
@@ -43,9 +44,14 @@ iD.svg.Areas = function(projection) {
areas = _.pluck(areas, 'entity');
+ var strokes = areas.filter(function (area) {
+ return area.type === 'way';
+ });
+
var fill = surface.select('.layer-fill'),
- stroke = surface.select('.layer-stroke'),
- fills = drawPaths(fill, areas, filter, 'way area fill'),
- strokes = drawPaths(stroke, areas, filter, 'way area stroke');
+ stroke = surface.select('.layer-stroke');
+
+ drawPaths(fill, areas, filter, 'fill');
+ drawPaths(stroke, strokes, filter, 'stroke');
};
};
diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js
index 9b2cbd1e9..31d2f03d5 100644
--- a/js/id/svg/labels.js
+++ b/js/id/svg/labels.js
@@ -335,9 +335,8 @@ iD.svg.Labels = function(projection) {
}
function getAreaLabel(entity, width, height) {
- var nodes = _.pluck(graph.childNodes(entity), 'loc')
- .map(iD.svg.RoundProjection(projection)),
- centroid = d3.geom.polygon(nodes).centroid(),
+ var path = d3.geo.path().projection(projection),
+ centroid = path.centroid(entity.asGeoJSON(graph)),
extent = entity.extent(graph),
entitywidth = projection(extent[1])[0] - projection(extent[0])[0];
diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js
index a1151204b..2fd7b9152 100644
--- a/js/id/svg/lines.js
+++ b/js/id/svg/lines.js
@@ -34,19 +34,25 @@ iD.svg.Lines = function(projection) {
}
return function drawLines(surface, graph, entities, filter) {
- function drawPaths(group, lines, filter, classes, lineString) {
+ function drawPaths(group, lines, filter, klass, lineString) {
+ var tagClasses = iD.svg.TagClasses();
+
+ if (klass === 'stroke') {
+ tagClasses.tags(iD.svg.MultipolygonMemberTags(graph));
+ }
+
var paths = group.selectAll('path.line')
.filter(filter)
.data(lines, iD.Entity.key);
paths.enter()
.append('path')
- .attr('class', classes);
+ .attr('class', 'way line ' + klass);
paths
.order()
.attr('d', lineString)
- .call(iD.svg.TagClasses())
+ .call(tagClasses)
.call(iD.svg.MemberClasses(graph));
paths.exit()
@@ -65,8 +71,7 @@ iD.svg.Lines = function(projection) {
container.remove();
}
- var lines = [],
- lineStrings = {};
+ var lines = [];
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
@@ -84,9 +89,9 @@ iD.svg.Lines = function(projection) {
stroke = surface.select('.layer-stroke'),
defs = surface.select('defs'),
text = surface.select('.layer-text'),
- shadows = drawPaths(shadow, lines, filter, 'way line shadow', lineString),
- casings = drawPaths(casing, lines, filter, 'way line casing', lineString),
- strokes = drawPaths(stroke, lines, filter, 'way line stroke', lineString);
+ shadows = drawPaths(shadow, lines, filter, 'shadow', lineString),
+ casings = drawPaths(casing, lines, filter, 'casing', lineString),
+ strokes = drawPaths(stroke, lines, filter, 'stroke', lineString);
// Determine the lengths of oneway paths
var lengths = {},
diff --git a/js/id/svg/multipolygons.js b/js/id/svg/multipolygons.js
deleted file mode 100644
index b4f050ad5..000000000
--- a/js/id/svg/multipolygons.js
+++ /dev/null
@@ -1,55 +0,0 @@
-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(graph) === '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())
- .call(iD.svg.MemberClasses(graph));
-
- 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 5ce585add..dad388ffe 100644
--- a/js/id/svg/tag_classes.js
+++ b/js/id/svg/tag_classes.js
@@ -3,10 +3,11 @@ iD.svg.TagClasses = function() {
'highway', 'railway', 'waterway', 'power', 'motorway', 'amenity',
'natural', 'landuse', 'building', 'oneway', 'bridge', 'boundary',
'leisure', 'construction'
- ]), tagClassRe = /^tag-/;
+ ]), tagClassRe = /^tag-/,
+ tags = function(entity) { return entity.tags; };
- return function tagClassesSelection(selection) {
- selection.each(function tagClassesEach(d, i) {
+ var tagClasses = function(selection) {
+ selection.each(function tagClassesEach(entity) {
var classes, value = this.className;
if (value.baseVal !== undefined) value = value.baseVal;
@@ -15,11 +16,10 @@ iD.svg.TagClasses = function() {
return name.length && !tagClassRe.test(name);
}).join(' ');
- var tags = d.tags;
- for (var k in tags) {
+ var t = tags(entity);
+ for (var k in t) {
if (!keys[k]) continue;
- classes += ' tag-' + k + ' ' +
- 'tag-' + k + '-' + tags[k];
+ classes += ' tag-' + k + ' ' + 'tag-' + k + '-' + t[k];
}
classes = classes.trim();
@@ -29,4 +29,12 @@ iD.svg.TagClasses = function() {
}
});
};
+
+ tagClasses.tags = function(_) {
+ if (!arguments.length) return tags;
+ tags = _;
+ return tagClasses;
+ };
+
+ return tagClasses;
};
diff --git a/test/index.html b/test/index.html
index 5f7c19b5a..522f26092 100644
--- a/test/index.html
+++ b/test/index.html
@@ -47,7 +47,6 @@
-
@@ -184,7 +183,6 @@
-
diff --git a/test/index_packaged.html b/test/index_packaged.html
index d21d48ff2..8dd73d666 100644
--- a/test/index_packaged.html
+++ b/test/index_packaged.html
@@ -66,7 +66,6 @@
-
diff --git a/test/spec/graph/relation.js b/test/spec/graph/relation.js
index 6e00dcacb..b07f9bee7 100644
--- a/test/spec/graph/relation.js
+++ b/test/spec/graph/relation.js
@@ -146,107 +146,135 @@ describe('iD.Relation', function () {
});
});
+ describe("#asGeoJSON", function (){
+ it('converts a multipolygon to a GeoJSON MultiPolygon feature', function() {
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
+ w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
+ r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}),
+ g = iD.Graph([a, b, c, w, r]),
+ json = r.asGeoJSON(g);
+
+ expect(json.type).to.equal('Feature');
+ expect(json.properties).to.eql({type: 'multipolygon'});
+ expect(json.geometry.type).to.equal('MultiPolygon');
+ expect(json.geometry.coordinates).to.eql([[[[1, 1], [2, 2], [3, 3], [1, 1]]]]);
+ });
+
+ it('converts a relation to a GeoJSON FeatureCollection', function() {
+ var a = iD.Node({loc: [1, 1]}),
+ r = iD.Relation({tags: {type: 'type'}, members: [{id: a.id, role: 'role'}]}),
+ g = iD.Graph([a, r]),
+ json = r.asGeoJSON(g);
+
+ expect(json.type).to.equal('FeatureCollection');
+ expect(json.properties).to.eql({type: 'type'});
+ expect(json.features).to.eql([_.extend({role: 'role'}, a.asGeoJSON(g))]);
+ });
+ });
+
describe("#multipolygon", function () {
specify("single polygon consisting of a single way", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({members: [{id: w.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, a]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]]]);
});
specify("single polygon consisting of multiple ways", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
- d = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
+ d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way({nodes: [c.id, d.id, a.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, d, a]]]); // TODO: not the only valid ordering
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc, a.loc]]]); // TODO: not the only valid ordering
});
specify("single polygon consisting of multiple ways, one needing reversal", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
- d = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
+ d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way({nodes: [a.id, d.id, c.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, d, a]]]); // TODO: not the only valid ordering
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc, a.loc]]]); // TODO: not the only valid ordering
});
specify("multiple polygons consisting of single ways", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
- d = iD.Node(),
- e = iD.Node(),
- f = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
+ d = iD.Node({loc: [4, 4]}),
+ e = iD.Node({loc: [5, 5]}),
+ f = iD.Node({loc: [6, 6]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
w2 = iD.Way({nodes: [d.id, e.id, f.id, d.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, e, f, w1, w2, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, a]], [[d, e, f, d]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]], [[d.loc, e.loc, f.loc, d.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of a single way", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id]}),
r = iD.Relation({members: [{id: w.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of multiple ways", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
- d = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
+ d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way({nodes: [c.id, d.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of multiple ways, alternate order", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
- d = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
+ d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [c.id, d.id]}),
w2 = iD.Way({nodes: [a.id, b.id, c.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of multiple ways, one needing reversal", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
- d = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
+ d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way({nodes: [d.id, c.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of multiple ways, one needing reversal, alternate order", function () {
@@ -259,7 +287,7 @@ describe('iD.Relation', function () {
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc]]]);
});
specify("single polygon with single single-way inner", function () {
@@ -274,7 +302,7 @@ describe('iD.Relation', function () {
r = iD.Relation({members: [{id: outer.id, type: 'way'}, {id: inner.id, role: 'inner', type: 'way'}]}),
g = iD.Graph([a, b, c, d, e, f, outer, inner, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, a], [d, e, f, d]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc]]]);
});
specify("single polygon with single multi-way inner", function () {
@@ -293,7 +321,7 @@ describe('iD.Relation', function () {
{id: inner1.id, role: 'inner', type: 'way'}]}),
graph = iD.Graph([a, b, c, d, e, f, outer, inner1, inner2, r]);
- expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d]]]);
+ expect(r.multipolygon(graph)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc]]]);
});
specify("single polygon with multiple single-way inners", function () {
@@ -315,7 +343,7 @@ describe('iD.Relation', function () {
{id: inner1.id, role: 'inner', type: 'way'}]}),
graph = iD.Graph([a, b, c, d, e, f, g, h, i, outer, inner1, inner2, r]);
- expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d], [g, h, i, g]]]);
+ expect(r.multipolygon(graph)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc], [g.loc, h.loc, i.loc, g.loc]]]);
});
specify("multiple polygons with single single-way inner", function () {
@@ -337,30 +365,30 @@ describe('iD.Relation', function () {
{id: inner.id, role: 'inner', type: 'way'}]}),
graph = iD.Graph([a, b, c, d, e, f, g, h, i, outer1, outer2, inner, r]);
- expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d]], [[g, h, i, g]]]);
+ expect(r.multipolygon(graph)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc]], [[g.loc, h.loc, i.loc, g.loc]]]);
});
specify("invalid geometry: unmatched inner", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({members: [{id: w.id, role: 'inner', type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c, a]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]]]);
});
specify("incomplete relation", function () {
- var a = iD.Node(),
- b = iD.Node(),
- c = iD.Node(),
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way(),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w1, r]);
- expect(r.multipolygon(g)).to.eql([[[a, b, c]]]);
+ expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc]]]);
});
});
});
diff --git a/test/spec/graph/way.js b/test/spec/graph/way.js
index 6b6b1543f..05864d5c3 100644
--- a/test/spec/graph/way.js
+++ b/test/spec/graph/way.js
@@ -95,8 +95,12 @@ describe('iD.Way', function() {
expect(iD.Way({tags: { area: 'yes' }}).isArea()).to.equal(true);
});
- it('returns true if the way is closed and has no tags', function() {
- expect(iD.Way({nodes: ['n1', 'n1']}).isArea()).to.equal(true);
+ it('returns false if the way is closed and has no tags', function() {
+ expect(iD.Way({nodes: ['n1', 'n1']}).isArea()).to.equal(false);
+ });
+
+ it('returns true if the way is closed and has tags', function() {
+ expect(iD.Way({nodes: ['n1', 'n1'], tags: {a: 'b'}}).isArea()).to.equal(true);
});
it('returns false if the way is closed and has tag area=no', function() {
@@ -207,7 +211,7 @@ describe('iD.Way', function() {
});
describe("#asGeoJSON", function () {
- it("converts to a GeoJSON LineString features", function () {
+ it("converts a line to a GeoJSON LineString features", function () {
var a = iD.Node({loc: [1, 2]}),
b = iD.Node({loc: [3, 4]}),
w = iD.Way({tags: {highway: 'residential'}, nodes: [a.id, b.id]}),
@@ -219,5 +223,19 @@ describe('iD.Way', function() {
expect(json.geometry.type).to.equal('LineString');
expect(json.geometry.coordinates).to.eql([[1, 2], [3, 4]]);
});
+
+ it("converts an area to a GeoJSON Polygon features", function () {
+ var a = iD.Node({loc: [1, 2]}),
+ b = iD.Node({loc: [3, 4]}),
+ c = iD.Node({loc: [5, 6]}),
+ w = iD.Way({tags: {area: 'yes'}, nodes: [a.id, b.id, c.id, a.id]}),
+ graph = iD.Graph([a, b, c, w]),
+ json = w.asGeoJSON(graph);
+
+ expect(json.type).to.equal('Feature');
+ expect(json.properties).to.eql({area: 'yes'});
+ expect(json.geometry.type).to.equal('Polygon');
+ expect(json.geometry.coordinates).to.eql([[[1, 2], [3, 4], [5, 6], [1, 2]]]);
+ });
});
});
diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js
index 807a799f4..bbf8674ae 100644
--- a/test/spec/svg/areas.js
+++ b/test/spec/svg/areas.js
@@ -69,4 +69,46 @@ describe("iD.svg.Areas", function () {
expect(surface.select('.area:nth-child(1)')).to.be.classed('tag-landuse-park');
expect(surface.select('.area:nth-child(2)')).to.be.classed('tag-building-yes');
});
+
+ it("renders fills for multipolygon areas", function () {
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
+ w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
+ r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}),
+ graph = iD.Graph([a, b, c, w, r]),
+ areas = [w, r];
+
+ surface.call(iD.svg.Areas(projection), graph, areas, filter);
+
+ expect(surface.select('.fill')).to.be.classed('relation');
+ });
+
+ it("renders no strokes for multipolygon areas", function () {
+ var a = iD.Node({loc: [1, 1]}),
+ b = iD.Node({loc: [2, 2]}),
+ c = iD.Node({loc: [3, 3]}),
+ w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
+ r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}),
+ graph = iD.Graph([a, b, c, w, r]),
+ areas = [w, r];
+
+ surface.call(iD.svg.Areas(projection), graph, areas, filter);
+
+ expect(surface.selectAll('.stroke')[0].length).to.equal(0);
+ });
+
+ it("adds stroke classes for the tags of the parent relation of multipolygon members", function() {
+ var a = iD.Node({loc: [1, 1]}),
+ 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'}}),
+ graph = iD.Graph([a, b, c, w, r]);
+
+ surface.call(iD.svg.Areas(projection), graph, [w], filter);
+
+ expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
+ expect(surface.select('.fill')).not.to.be.classed('tag-natural-wood');
+ });
});
diff --git a/test/spec/svg/lines.js b/test/spec/svg/lines.js
index 61c3229a9..989022632 100644
--- a/test/spec/svg/lines.js
+++ b/test/spec/svg/lines.js
@@ -39,6 +39,16 @@ describe("iD.svg.Lines", function () {
expect(surface.select('.line')).to.be.classed('member-type-route');
});
+ it("adds stroke classes for the tags of the parent relation of multipolygon members", function() {
+ var line = iD.Way(),
+ relation = iD.Relation({members: [{id: line.id}], tags: {type: 'multipolygon', natural: 'wood'}}),
+ graph = iD.Graph([line, relation]);
+
+ surface.call(iD.svg.Lines(projection), graph, [line], filter);
+
+ expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
+ });
+
it("preserves non-line paths", function () {
var line = iD.Way(),
graph = iD.Graph([line]);
diff --git a/test/spec/svg/multipolygons.js b/test/spec/svg/multipolygons.js
deleted file mode 100644
index 67f44ebae..000000000
--- a/test/spec/svg/multipolygons.js
+++ /dev/null
@@ -1,43 +0,0 @@
-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/tag_classes.js b/test/spec/svg/tag_classes.js
index dd899e08e..048378815 100644
--- a/test/spec/svg/tag_classes.js
+++ b/test/spec/svg/tag_classes.js
@@ -19,6 +19,13 @@ describe("iD.svg.TagClasses", function () {
expect(selection.attr('class')).to.equal('tag-highway tag-highway-primary');
});
+ it('adds tags based on the result of the `tags` accessor', function() {
+ selection
+ .datum(iD.Entity())
+ .call(iD.svg.TagClasses().tags(d3.functor({highway: 'primary'})));
+ expect(selection.attr('class')).to.equal('tag-highway tag-highway-primary');
+ });
+
it('removes classes for tags that are no longer present', function() {
selection
.attr('class', 'tag-highway tag-highway-primary')