From 4eccd7015f8380dae44a85543c6321fe2dcff901 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 25 Apr 2013 18:41:49 -0400 Subject: [PATCH 1/2] corner destroying resampling for oneway markers --- css/map.css | 7 ++----- js/id/svg.js | 41 +++++++++++++++++++++++++++++------------ js/id/svg/lines.js | 14 ++++++++------ js/id/svg/surface.js | 5 +++-- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/css/map.css b/css/map.css index a65a08198..a1324cc48 100644 --- a/css/map.css +++ b/css/map.css @@ -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; } /* diff --git a/js/id/svg.js b/js/id/svg.js index 3a15c9c49..d574c3dda 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -24,6 +24,7 @@ iD.svg = { var last, next, started = false, + offset = 0, d = ''; d3.geo.stream({ @@ -32,29 +33,45 @@ iD.svg = { return n.loc; }) }, projection.stream({ - lineStart: function() { last = null; started = false; }, + lineStart: function() { last = null; started = false; offset = 0; }, 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) { + + if (!started) { + d += 'M'; + d += next[0] + ',' + next[1]; + offset = dx; + + } else if (!dx) { + d += 'L'; + d += next[0] + ',' + next[1]; + } + + + // Resample + if (dx && last) { + 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)) { + + to[0] += Math.cos(angle) * offset; + to[1] += Math.sin(angle) * offset; + + 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; + d += 'L' + Math.floor(to[0]) + ',' + Math.floor(to[1]); to[0] += Math.cos(angle) * dx; to[1] += Math.sin(angle) * dx; } + + offset = iD.geo.dist(last, to) - span; } - if (started) d += 'L'; - d += next[0] + ',' + next[1]; - started = started || true; + + started = true; last = next; } })); diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index eeee76c3b..8219f1b93 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -112,14 +112,16 @@ iD.svg.Lines = function(projection) { casing = surface.select('.layer-casing'), stroke = surface.select('.layer-stroke'), defs = surface.select('defs'), - text = surface.select('.layer-text'), + oneway = surface.select('.layer-oneway'), shadows = drawPaths(shadow, lines, filter, 'shadow', lineString), casings = drawPaths(casing, lines, filter, 'casing', lineString), - strokes = drawPaths(stroke, lines, filter, 'stroke', lineString); + strokes = drawPaths(stroke, lines, filter, 'stroke', lineString), + oneways = drawPaths(oneway, lines, filter, 'oneway', lineStringOneway); - strokes - .filter(function(d) { return d.isOneWay(); }) - .attr('marker-mid', 'url(#oneway-marker)') - .attr('d', lineStringResampled); + oneways.attr('marker-mid', 'url(#oneway-marker)'); + + function lineStringOneway(d) { + return d.isOneWay() && lineStringResampled(d); + } }; }; diff --git a/js/id/svg/surface.js b/js/id/svg/surface.js index 8c3123366..73db36475 100644 --- a/js/id/svg/surface.js +++ b/js/id/svg/surface.js @@ -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; }); From 6941d1914b5deccd9d2f43914ce22810573b91a9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 26 Apr 2013 16:58:36 -0700 Subject: [PATCH 2/2] Perfect oneway markers --- js/id/renderer/map.js | 2 +- js/id/svg.js | 90 ++++++++++++++++++++---------------------- js/id/svg/lines.js | 36 +++++++++++------ test/spec/svg.js | 39 ------------------ test/spec/svg/lines.js | 14 +++---- 5 files changed, 74 insertions(+), 107 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index bd3f405c0..846c106cf 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -127,7 +127,7 @@ iD.Map = function(context) { surface .call(points, graph, all, filter) .call(vertices, graph, visibleEntities(), 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); diff --git a/js/id/svg.js b/js/id/svg.js index d574c3dda..f44b53dc0 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -13,19 +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, - offset = 0, - d = ''; + OneWaySegments: function(projection, graph, dt) { + return function(entity) { + var a, + b, + i = 0, + offset = dt, + segments = []; d3.geo.stream({ type: 'LineString', @@ -33,56 +37,46 @@ iD.svg = { return n.loc; }) }, projection.stream({ - lineStart: function() { last = null; started = false; offset = 0; }, - lineEnd: function() { }, + lineStart: function() {}, + lineEnd: function() {}, point: function(x, y) { + b = [x, y]; - next = [Math.floor(x), Math.floor(y)]; + if (a) { + var segment = 'M' + a[0] + ',' + a[1]; - if (!started) { - d += 'M'; - d += next[0] + ',' + next[1]; - offset = dx; + 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; - } else if (!dx) { - d += 'L'; - d += next[0] + ',' + next[1]; - } + if (offset < span) { + p = [a[0] + offset * Math.cos(angle), + a[1] + offset * Math.sin(angle)]; - - // Resample - if (dx && last) { - - 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) * offset; - to[1] += Math.sin(angle) * offset; - - while (iD.geo.dist(last, to) < span) { - - // a dx-length line segment in that angle - d += 'L' + Math.floor(to[0]) + ',' + Math.floor(to[1]); - to[0] += Math.cos(angle) * dx; - to[1] += Math.sin(angle) * dx; + segment += 'L' + p[0] + ',' + p[1]; } - offset = iD.geo.dist(last, to) - span; + 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++; } - 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; }; }, diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 8219f1b93..f3dfc7262 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -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,23 +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'), - oneway = surface.select('.layer-oneway'), - shadows = drawPaths(shadow, lines, filter, 'shadow', lineString), - casings = drawPaths(casing, lines, filter, 'casing', lineString), - strokes = drawPaths(stroke, lines, filter, 'stroke', lineString), - oneways = drawPaths(oneway, lines, filter, 'oneway', lineStringOneway); + oneway = surface.select('.layer-oneway'); - oneways.attr('marker-mid', 'url(#oneway-marker)'); + drawPaths(shadow, lines, filter, 'shadow', lineString); + drawPaths(casing, lines, filter, 'casing', lineString); + drawPaths(stroke, lines, filter, 'stroke', lineString); - function lineStringOneway(d) { - return d.isOneWay() && lineStringResampled(d); - } + 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(); }; }; diff --git a/test/spec/svg.js b/test/spec/svg.js index 72efc293d..e69de29bb 100644 --- a/test/spec/svg.js +++ b/test/spec/svg.js @@ -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; - }); -}); diff --git a/test/spec/svg/lines.js b/test/spec/svg/lines.js index 02e864dda..61dfe1912 100644 --- a/test/spec/svg/lines.js +++ b/test/spec/svg/lines.js @@ -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); });