Merge branch 'perfect-resampling'

Conflicts:
	js/id/renderer/map.js
This commit is contained in:
John Firebaugh
2013-04-26 17:35:04 -07:00
7 changed files with 86 additions and 102 deletions

View File

@@ -823,11 +823,8 @@ marker#oneway-marker path {
opacity: .5;
}
text.tag-oneway {
fill:#91CFFF;
stroke:#2C6B9B;
stroke-width:1;
pointer-events:none;
path.oneway {
stroke-width: 6px;
}
/*

View File

@@ -123,7 +123,7 @@ iD.Map = function(context) {
surface
.call(points, graph, all, filter)
.call(vertices, graph, all, filter, map.zoom())
.call(lines, graph, all, filter, dimensions)
.call(lines, graph, all, filter)
.call(areas, graph, all, filter)
.call(midpoints, graph, all, filter, extent)
.call(labels, graph, all, filter, dimensions, !difference);

View File

@@ -13,18 +13,23 @@ iD.svg = {
};
},
LineString: function(projection, graph, dimensions, dx) {
var cache = {};
LineString: function(projection, graph) {
var cache = {},
path = d3.geo.path().projection(projection);
return function(entity) {
if (cache[entity.id] !== undefined) {
return cache[entity.id];
}
if (entity.id in cache) return cache[entity.id];
return cache[entity.id] = path(entity.asGeoJSON(graph));
};
},
var last,
next,
started = false,
d = '';
OneWaySegments: function(projection, graph, dt) {
return function(entity) {
var a,
b,
i = 0,
offset = dt,
segments = [];
d3.geo.stream({
type: 'LineString',
@@ -32,40 +37,46 @@ iD.svg = {
return n.loc;
})
}, projection.stream({
lineStart: function() { last = null; started = false; },
lineEnd: function() { },
lineStart: function() {},
lineEnd: function() {},
point: function(x, y) {
if (!started) d += 'M';
next = [Math.floor(x), Math.floor(y)];
if (dx && last && iD.geo.dist(last, next) > dx) {
var span = iD.geo.dist(last, next),
angle = Math.atan2(next[1] - last[1], next[0] - last[0]),
to = last.slice();
to[0] += Math.cos(angle) * dx;
to[1] += Math.sin(angle) * dx;
while (iD.geo.dist(last, to) < (span)) {
// a dx-length line segment in that angle
if (started) d += 'L';
d += Math.floor(to[0]) + ',' + Math.floor(to[1]);
started = started || true;
to[0] += Math.cos(angle) * dx;
to[1] += Math.sin(angle) * dx;
b = [x, y];
if (a) {
var segment = 'M' + a[0] + ',' + a[1];
var span = iD.geo.dist(a, b),
angle = Math.atan2(b[1] - a[1], b[0] - a[0]),
dx = dt * Math.cos(angle),
dy = dt * Math.sin(angle),
p;
if (offset < span) {
p = [a[0] + offset * Math.cos(angle),
a[1] + offset * Math.sin(angle)];
segment += 'L' + p[0] + ',' + p[1];
}
while ((offset + dt) < span) {
offset += dt;
p[0] += dx;
p[1] += dy;
segment += 'L' + p[0] + ',' + p[1];
}
offset = dt - (span - offset);
segment += 'L' + b[0] + ',' + b[1];
segments.push({id: entity.id, index: i, d: segment});
i++;
}
if (started) d += 'L';
d += next[0] + ',' + next[1];
started = started || true;
last = next;
a = b;
}
}));
if (d === '') {
cache[entity.id] = null;
return cache[entity.id];
} else {
cache[entity.id] = d;
return cache[entity.id];
}
return segments;
};
},

View File

@@ -59,7 +59,7 @@ iD.svg.Lines = function(projection) {
return outer;
}
return function drawLines(surface, graph, entities, filter, dimensions) {
return function drawLines(surface, graph, entities, filter) {
function drawPaths(group, lines, filter, klass, lineString) {
lines = lines.filter(function(line) {
return lineString(line);
@@ -105,21 +105,35 @@ iD.svg.Lines = function(projection) {
lines.sort(waystack);
var lineString = iD.svg.LineString(projection, graph, dimensions);
var lineStringResampled = iD.svg.LineString(projection, graph, dimensions, 35);
var lineString = iD.svg.LineString(projection, graph);
var shadow = surface.select('.layer-shadow'),
casing = surface.select('.layer-casing'),
stroke = surface.select('.layer-stroke'),
defs = surface.select('defs'),
text = surface.select('.layer-text'),
shadows = drawPaths(shadow, lines, filter, 'shadow', lineString),
casings = drawPaths(casing, lines, filter, 'casing', lineString),
strokes = drawPaths(stroke, lines, filter, 'stroke', lineString);
oneway = surface.select('.layer-oneway');
strokes
.filter(function(d) { return d.isOneWay(); })
.attr('marker-mid', 'url(#oneway-marker)')
.attr('d', lineStringResampled);
drawPaths(shadow, lines, filter, 'shadow', lineString);
drawPaths(casing, lines, filter, 'casing', lineString);
drawPaths(stroke, lines, filter, 'stroke', lineString);
var segments = _.flatten(lines
.filter(function(d) { return d.isOneWay(); })
.map(iD.svg.OneWaySegments(projection, graph, 35)));
var oneways = oneway.selectAll('path.oneway')
.data(segments, function(d) { return [d.id, d.index]; });
oneways.enter()
.append('path')
.attr('class', 'oneway')
.attr('marker-mid', 'url(#oneway-marker)');
oneways
.order()
.attr('d', function(d) { return d.d; });
oneways.exit()
.remove();
};
};

View File

@@ -34,12 +34,13 @@ iD.svg.Surface = function(context) {
id: 'oneway-marker',
viewBox: '0 0 10 10',
refY: 2.5,
refX: 5,
markerWidth: 2,
markerHeight: 2,
orient: 'auto'
})
.append('path')
.attr('d', 'M 0 0 L 5 2.5 L 0 5 z');
.attr('d', 'M 5 3 L 0 3 L 0 2 L 5 2 L 5 0 L 10 2.5 L 5 5 z');
var patterns = defs.selectAll('pattern')
.data([
@@ -109,7 +110,7 @@ iD.svg.Surface = function(context) {
maki));
var layers = selection.selectAll('.layer')
.data(['fill', 'shadow', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']);
.data(['fill', 'shadow', 'casing', 'stroke', 'oneway', 'hit', 'halo', 'label']);
layers.enter().append('g')
.attr('class', function(d) { return 'layer layer-' + d; });

View File

@@ -1,39 +0,0 @@
describe("iD.svg.LineString", function () {
var projection = d3.geo.mercator().scale(250 / Math.PI);
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.id, b.id]}),
graph = iD.Graph([a, b, way]);
expect(iD.svg.LineString(projection, graph, [10, 10])(way)).to.equal('M480,250L482,245');
});
describe('resampling', function() {
it("resamples a linestring", function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [10, 0]}),
way = iD.Way({nodes: [a.id, b.id]}),
graph = iD.Graph([a, b, way]);
expect(iD.svg.LineString(projection, graph, [10, 10], 2)(way)).to.equal('M480,250L482,250L484,250L486,250L488,250L490,250L492,250L493,250');
});
it("does not resmample when no steps are possible", function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [10, 0]}),
way = iD.Way({nodes: [a.id, b.id]}),
graph = iD.Graph([a, b, way]);
expect(iD.svg.LineString(projection, graph, [10, 10], 20)(way)).to.equal('M480,250L493,250');
});
});
it("returns null for an entity with no nodes", function () {
var way = iD.Way(),
graph = iD.Graph([way]);
expect(iD.svg.LineString(projection, graph, [10, 10])(way)).to.be.null;
});
});

View File

@@ -15,7 +15,7 @@ describe("iD.svg.Lines", function () {
line = iD.Way({nodes: [a.id, b.id]}),
graph = iD.Graph([a, b, line]);
surface.call(iD.svg.Lines(projection), graph, [line], filter, dimensions);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
expect(surface.select('path.way')).to.be.classed('way');
expect(surface.select('path.line')).to.be.classed('line');
@@ -27,7 +27,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, dimensions);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
expect(surface.select('.line')).to.be.classed('tag-highway');
expect(surface.select('.line')).to.be.classed('tag-highway-residential');
@@ -40,7 +40,7 @@ describe("iD.svg.Lines", function () {
relation = iD.Relation({members: [{id: line.id}], tags: {type: 'route'}}),
graph = iD.Graph([a, b, line, relation]);
surface.call(iD.svg.Lines(projection), graph, [line], filter, dimensions);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
expect(surface.select('.line')).to.be.classed('member');
expect(surface.select('.line')).to.be.classed('member-type-route');
@@ -53,7 +53,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, dimensions);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
});
@@ -66,7 +66,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, dimensions);
surface.call(iD.svg.Lines(projection), graph, [w], filter);
expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
});
@@ -80,7 +80,7 @@ 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, dimensions);
surface.call(iD.svg.Lines(projection), graph, [i], filter);
expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
});
@@ -93,7 +93,7 @@ describe("iD.svg.Lines", function () {
.append('path')
.attr('class', 'other');
surface.call(iD.svg.Lines(projection), graph, [line], filter, dimensions);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
expect(surface.selectAll('.other')[0].length).to.equal(1);
});