Merge branch 'refs/heads/bhousel-bhousel-layers'

This commit is contained in:
John Firebaugh
2014-06-13 12:03:16 -07:00
8 changed files with 271 additions and 98 deletions
+54 -14
View File
@@ -774,42 +774,82 @@ path.casing.tag-boundary-national_park {
/* bridges */
path.casing.tag-bridge {
stroke-width: 14;
stroke-opacity: 0.5;
stroke-width: 16;
stroke-opacity: 0.6;
stroke: #000;
stroke-linecap: butt;
}
path.shadow.tag-bridge {
stroke-width: 22;
}
path.casing.line.tag-railway.tag-bridge,
path.casing.tag-highway-living_street.tag-bridge,
path.casing.tag-highway-path.tag-bridge {
stroke-width: 6;
}
path.casing.line.tag-highway-pedestrian,
path.casing.tag-highway-path.tag-bridge,
path.casing.line.tag-highway-pedestrian.tag-bridge,
path.casing.tag-highway-service.tag-bridge,
path.casing.tag-highway-track.tag-bridge,
path.casing.tag-highway-steps.tag-bridge,
path.casing.tag-highway-footway.tag-bridge,
path.casing.tag-highway-cycleway.tag-bridge,
path.casing.tag-highway-bridleway.tag-bridge {
stroke-width: 8;
}
path.shadow.tag-highway-residential.tag-bridge {
stroke-width:22;
stroke-width: 10;
}
path.shadow.line.tag-railway.tag-bridge,
path.shadow.tag-highway-living_street.tag-bridge,
path.shadow.tag-highway-path.tag-bridge,
path.shadow.line.tag-highway-pedestrian,
path.shadow.line.tag-highway-pedestrian.tag-bridge,
path.shadow.tag-highway-service.tag-bridge,
path.shadow.tag-highway-track.tag-bridge,
path.shadow.tag-highway-steps.tag-bridge,
path.shadow.tag-highway-footway.tag-bridge,
path.shadow.tag-highway-cycleway.tag-bridge,
path.shadow.tag-highway-bridleway.tag-bridge {
stroke-width: 16;
stroke-width: 17;
}
.low-zoom path.casing.tag-bridge {
stroke-width: 10;
stroke-opacity: 0.6;
stroke: #000;
stroke-linecap: butt;
}
.low-zoom path.shadow.tag-bridge {
stroke-width: 14;
}
.low-zoom path.casing.line.tag-railway.tag-bridge,
.low-zoom path.casing.tag-highway-living_street.tag-bridge,
.low-zoom path.casing.tag-highway-path.tag-bridge,
.low-zoom path.casing.line.tag-highway-pedestrian.tag-bridge,
.low-zoom path.casing.tag-highway-service.tag-bridge,
.low-zoom path.casing.tag-highway-track.tag-bridge,
.low-zoom path.casing.tag-highway-steps.tag-bridge,
.low-zoom path.casing.tag-highway-footway.tag-bridge,
.low-zoom path.casing.tag-highway-cycleway.tag-bridge,
.low-zoom path.casing.tag-highway-bridleway.tag-bridge {
stroke-width: 6;
}
.low-zoom path.shadow.line.tag-railway.tag-bridge,
.low-zoom path.shadow.tag-highway-living_street.tag-bridge,
.low-zoom path.shadow.tag-highway-path.tag-bridge,
.low-zoom path.shadow.line.tag-highway-pedestrian.tag-bridge,
.low-zoom path.shadow.tag-highway-service.tag-bridge,
.low-zoom path.shadow.tag-highway-track.tag-bridge,
.low-zoom path.shadow.tag-highway-steps.tag-bridge,
.low-zoom path.shadow.tag-highway-footway.tag-bridge,
.low-zoom path.shadow.tag-highway-cycleway.tag-bridge,
.low-zoom path.shadow.tag-highway-bridleway.tag-bridge {
stroke-width: 13;
}
/* tunnels */
path.stroke.tag-tunnel {
+23
View File
@@ -42,6 +42,29 @@ _.extend(iD.Way.prototype, {
if (this.nodes[this.nodes.length - 1] === node) return 'suffix';
},
layer: function() {
// explicit layer tag, clamp between -10, 10..
if (this.tags.layer !== undefined) {
return Math.max(-10, Math.min(+(this.tags.layer), 10));
}
// implied layer tag..
if (this.tags.location === 'overground') return 1;
if (this.tags.location === 'underground') return -1;
if (this.tags.location === 'underwater') return -10;
if (this.tags.power === 'line') return 10;
if (this.tags.power === 'minor_line') return 10;
if (this.tags.aerialway) return 10;
if (this.tags.bridge) return 1;
if (this.tags.cutting) return -1;
if (this.tags.tunnel) return -1;
if (this.tags.waterway) return -1;
if (this.tags.man_made === 'pipeline') return -10;
if (this.tags.boundary) return -10;
return 0;
},
isOneWay: function() {
// explicit oneway tag..
if (['yes', '1', '-1'].indexOf(this.tags.oneway) !== -1) { return true; }
+12 -3
View File
@@ -64,8 +64,17 @@ iD.svg.Areas = function(projection) {
fill: areas
};
var paths = surface.selectAll('.layer-shadow, .layer-stroke, .layer-fill')
.selectAll('path.area')
var areagroup = surface
.select('.layer-areas')
.selectAll('g.areagroup')
.data(['fill', 'shadow', 'stroke']);
areagroup.enter()
.append('g')
.attr('class', function(d) { return 'layer areagroup area-' + d; });
var paths = areagroup
.selectAll('path')
.filter(filter)
.data(function(layer) { return data[layer]; }, iD.Entity.key);
@@ -74,7 +83,7 @@ iD.svg.Areas = function(projection) {
paths.exit()
.remove();
var fills = surface.selectAll('.layer-fill path.area')[0];
var fills = surface.selectAll('.area-fill path.area')[0];
var bisect = d3.bisector(function(node) {
return -node.__data__.area(graph);
+70 -52
View File
@@ -16,80 +16,98 @@ iD.svg.Lines = function(projection) {
};
function waystack(a, b) {
if (!a || !b || !a.tags || !b.tags) return 0;
if (a.tags.layer !== undefined && b.tags.layer !== undefined) {
return a.tags.layer - b.tags.layer;
}
if (a.tags.bridge) return 1;
if (b.tags.bridge) return -1;
if (a.tags.tunnel) return -1;
if (b.tags.tunnel) return 1;
var as = 0, bs = 0;
if (a.tags.highway && b.tags.highway) {
as -= highway_stack[a.tags.highway];
bs -= highway_stack[b.tags.highway];
}
if (a.tags.highway) { as -= highway_stack[a.tags.highway]; }
if (b.tags.highway) { bs -= highway_stack[b.tags.highway]; }
return as - bs;
}
return function drawLines(surface, graph, entities, filter) {
var lines = [],
path = iD.svg.Path(projection, graph);
var ways = [], pathdata = {}, onewaydata = {},
getPath = iD.svg.Path(projection, graph);
for (var i = 0; i < entities.length; i++) {
var entity = entities[i],
outer = iD.geo.simpleMultipolygonOuterMember(entity, graph);
if (outer) {
lines.push(entity.mergeTags(outer.tags));
ways.push(entity.mergeTags(outer.tags));
} else if (entity.geometry(graph) === 'line') {
lines.push(entity);
ways.push(entity);
}
}
lines = lines.filter(path);
lines.sort(waystack);
ways = ways.filter(getPath);
function drawPaths(klass) {
var paths = surface.select('.layer-' + klass)
.selectAll('path.line')
.filter(filter)
.data(lines, iD.Entity.key);
pathdata = _.groupBy(ways, function(way) { return way.layer(); });
var enter = paths.enter()
.append('path')
.attr('class', function(d) { return 'way line ' + klass + ' ' + d.id; });
_.forOwn(pathdata, function(v, k) {
onewaydata[k] = _(v)
.filter(function(d) { return d.isOneWay(); })
.map(iD.svg.OneWaySegments(projection, graph, 35))
.flatten()
.valueOf();
});
// Optimization: call simple TagClasses only on enter selection. This
// works because iD.Entity.key is defined to include the entity v attribute.
if (klass !== 'stroke') {
enter.call(iD.svg.TagClasses());
} else {
paths.call(iD.svg.TagClasses()
.tags(iD.svg.MultipolygonMemberTags(graph)));
}
var layergroup = surface
.select('.layer-lines')
.selectAll('g.layergroup')
.data(d3.range(-10, 11));
paths
.order()
.attr('d', path);
layergroup.enter()
.append('g')
.attr('class', function(d) { return 'layer layergroup layer' + String(d); });
paths.exit()
.remove();
}
drawPaths('shadow');
drawPaths('casing');
drawPaths('stroke');
var linegroup = layergroup
.selectAll('g.linegroup')
.data(['shadow', 'casing', 'stroke']);
var segments = _(lines)
.filter(function(d) { return d.isOneWay(); })
.map(iD.svg.OneWaySegments(projection, graph, 35))
.flatten()
.valueOf();
linegroup.enter()
.append('g')
.attr('class', function(d) { return 'layer linegroup line-' + d; });
var oneways = surface.select('.layer-oneway')
.selectAll('path.oneway')
var lines = linegroup
.selectAll('path')
.filter(filter)
.data(segments, function(d) { return [d.id, d.index]; });
.data(
function() { return pathdata[this.parentNode.parentNode.__data__] || []; },
iD.Entity.key
);
// Optimization: call simple TagClasses only on enter selection. This
// works because iD.Entity.key is defined to include the entity v attribute.
lines.enter()
.append('path')
.attr('class', function(d) { return 'way line ' + this.parentNode.__data__ + ' ' + d.id; })
.call(iD.svg.TagClasses());
lines
.sort(waystack)
.attr('d', getPath)
.call(iD.svg.TagClasses().tags(iD.svg.MultipolygonMemberTags(graph)));
lines.exit()
.remove();
var onewaygroup = layergroup
.selectAll('g.onewaygroup')
.data(['oneway']);
onewaygroup.enter()
.append('g')
.attr('class', 'layer onewaygroup');
var oneways = onewaygroup
.selectAll('path')
.filter(filter)
.data(
function() { return onewaydata[this.parentNode.parentNode.__data__] || []; },
function(d) { return [d.id, d.index]; }
);
oneways.enter()
.append('path')
@@ -97,10 +115,10 @@ iD.svg.Lines = function(projection) {
.attr('marker-mid', 'url(#oneway-marker)');
oneways
.order()
.attr('d', function(d) { return d.d; });
oneways.exit()
.remove();
};
};
+1 -1
View File
@@ -1,7 +1,7 @@
iD.svg.Surface = function() {
return function (selection) {
var layers = selection.selectAll('.layer')
.data(['fill', 'shadow', 'casing', 'stroke', 'oneway', 'hit', 'halo', 'label']);
.data(['areas', 'lines', 'hit', 'halo', 'label']);
layers.enter().append('g')
.attr('class', function(d) { return 'layer layer-' + d; });
+62
View File
@@ -148,6 +148,68 @@ describe('iD.Way', function() {
});
});
describe('#layer', function() {
it('returns 0 when the way has no tags', function() {
expect(iD.Way().layer()).to.equal(0);
});
it('returns the layer when the way has an explicit layer tag', function() {
expect(iD.Way({tags: { layer: '2' }}).layer()).to.equal(2);
expect(iD.Way({tags: { layer: '-5' }}).layer()).to.equal(-5);
});
it('clamps the layer to within -10, 10', function() {
expect(iD.Way({tags: { layer: '12' }}).layer()).to.equal(10);
expect(iD.Way({tags: { layer: '-15' }}).layer()).to.equal(-10);
});
it('returns 1 for location=overground', function() {
expect(iD.Way({tags: { location: 'overground' }}).layer()).to.equal(1);
});
it('returns -1 for location=underground', function() {
expect(iD.Way({tags: { location: 'underground' }}).layer()).to.equal(-1);
});
it('returns -10 for location=underwater', function() {
expect(iD.Way({tags: { location: 'underwater' }}).layer()).to.equal(-10);
});
it('returns 10 for power lines', function() {
expect(iD.Way({tags: { power: 'line' }}).layer()).to.equal(10);
expect(iD.Way({tags: { power: 'minor_line' }}).layer()).to.equal(10);
});
it('returns 10 for aerialways', function() {
expect(iD.Way({tags: { aerialway: 'cable_car' }}).layer()).to.equal(10);
});
it('returns 1 for bridges', function() {
expect(iD.Way({tags: { bridge: 'yes' }}).layer()).to.equal(1);
});
it('returns -1 for cuttings', function() {
expect(iD.Way({tags: { cutting: 'yes' }}).layer()).to.equal(-1);
});
it('returns -1 for tunnels', function() {
expect(iD.Way({tags: { tunnel: 'yes' }}).layer()).to.equal(-1);
});
it('returns -1 for waterways', function() {
expect(iD.Way({tags: { waterway: 'stream' }}).layer()).to.equal(-1);
});
it('returns -10 for pipelines', function() {
expect(iD.Way({tags: { man_made: 'pipeline' }}).layer()).to.equal(-10);
});
it('returns -10 for boundaries', function() {
expect(iD.Way({tags: { boundary: 'administrative' }}).layer()).to.equal(-10);
});
});
describe('#isOneWay', function() {
it('returns false when the way has no tags', function() {
expect(iD.Way().isOneWay()).to.be.false;
-13
View File
@@ -40,19 +40,6 @@ describe("iD.svg.Areas", function () {
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], none);
expect(surface.selectAll('.other')[0].length).to.equal(1);
});
it("handles deletion of a way and a member vertex (#1903)", function () {
var graph = iD.Graph([
iD.Node({id: 'a', loc: [0, 0]}),
+49 -15
View File
@@ -2,20 +2,21 @@ describe("iD.svg.Lines", function () {
var surface,
projection = d3.geo.projection(function(x, y) { return [x, y]; })
.clipExtent([[0, 0], [Infinity, Infinity]]),
filter = d3.functor(true);
all = d3.functor(true),
none = d3.functor(false);
beforeEach(function () {
surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg'))
.call(iD.svg.Surface(iD()));
});
it("adds way and area classes", function () {
it("adds way and line classes", function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [1, 1]}),
line = iD.Way({nodes: [a.id, b.id]}),
graph = iD.Graph([a, b, line]);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
surface.call(iD.svg.Lines(projection), graph, [line], all);
expect(surface.select('path.way')).to.be.classed('way');
expect(surface.select('path.line')).to.be.classed('line');
@@ -27,7 +28,7 @@ describe("iD.svg.Lines", function () {
line = iD.Way({nodes: [a.id, b.id], tags: {highway: 'residential'}}),
graph = iD.Graph([a, b, line]);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
surface.call(iD.svg.Lines(projection), graph, [line], all);
expect(surface.select('.line')).to.be.classed('tag-highway');
expect(surface.select('.line')).to.be.classed('tag-highway-residential');
@@ -40,7 +41,7 @@ describe("iD.svg.Lines", function () {
relation = iD.Relation({members: [{id: line.id}], tags: {type: 'multipolygon', natural: 'wood'}}),
graph = iD.Graph([a, b, line, relation]);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
surface.call(iD.svg.Lines(projection), graph, [line], all);
expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
});
@@ -53,7 +54,7 @@ describe("iD.svg.Lines", function () {
r = iD.Relation({members: [{id: w.id}], tags: {type: 'multipolygon'}}),
graph = iD.Graph([a, b, c, w, r]);
surface.call(iD.svg.Lines(projection), graph, [w], filter);
surface.call(iD.svg.Lines(projection), graph, [w], all);
expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
});
@@ -67,21 +68,54 @@ describe("iD.svg.Lines", function () {
r = iD.Relation({members: [{id: o.id, role: 'outer'}, {id: i.id, role: 'inner'}], tags: {type: 'multipolygon'}}),
graph = iD.Graph([a, b, c, o, i, r]);
surface.call(iD.svg.Lines(projection), graph, [i], filter);
surface.call(iD.svg.Lines(projection), graph, [i], all);
expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
});
it("preserves non-line paths", function () {
var line = iD.Way(),
graph = iD.Graph([line]);
describe("z-indexing", function() {
var graph = iD.Graph([
iD.Node({id: 'a', loc: [0, 0]}),
iD.Node({id: 'b', loc: [1, 1]}),
iD.Node({id: 'c', loc: [0, 0]}),
iD.Node({id: 'd', loc: [1, 1]}),
iD.Way({id: 'lo', tags: {highway: 'residential', tunnel: 'yes'}, nodes: ['a', 'b']}),
iD.Way({id: 'hi', tags: {highway: 'residential', bridge: 'yes'}, nodes: ['c', 'd']})
]);
surface.select('.layer-fill')
.append('path')
.attr('class', 'other');
it("stacks higher lines above lower ones in a single render", function () {
surface.call(iD.svg.Lines(projection), graph, [graph.entity('lo'), graph.entity('hi')], none);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
var selection = surface.selectAll('g.line-stroke > path.line');
expect(selection[0][0].__data__.id).to.eql('lo');
expect(selection[0][1].__data__.id).to.eql('hi');
});
expect(surface.selectAll('.other')[0].length).to.equal(1);
it("stacks higher lines above lower ones in a single render (reverse)", function () {
surface.call(iD.svg.Lines(projection), graph, [graph.entity('hi'), graph.entity('lo')], none);
var selection = surface.selectAll('g.line-stroke > path.line');
expect(selection[0][0].__data__.id).to.eql('lo');
expect(selection[0][1].__data__.id).to.eql('hi');
});
it("stacks higher lines above lower ones in separate renders", function () {
surface.call(iD.svg.Lines(projection), graph, [graph.entity('lo')], none);
surface.call(iD.svg.Lines(projection), graph, [graph.entity('hi')], none);
var selection = surface.selectAll('g.line-stroke > path.line');
expect(selection[0][0].__data__.id).to.eql('lo');
expect(selection[0][1].__data__.id).to.eql('hi');
});
it("stacks higher lines above lower in separate renders (reverse)", function () {
surface.call(iD.svg.Lines(projection), graph, [graph.entity('hi')], none);
surface.call(iD.svg.Lines(projection), graph, [graph.entity('lo')], none);
var selection = surface.selectAll('g.line-stroke > path.line');
expect(selection[0][0].__data__.id).to.eql('lo');
expect(selection[0][1].__data__.id).to.eql('hi');
});
});
});