From 27c0edb2c10409ee73681d18c5b59c48ca003117 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Mar 2013 21:08:50 -0500 Subject: [PATCH 1/5] Resample oneway paths to produce markers. This uses a technique created by @mbostock in http://bl.ocks.org/mbostock/4965670 Need to confirm that this is faster. It is definitely cleaner. --- css/map.css | 11 +++------ js/id/svg.js | 15 +++++++++++ js/id/svg/lines.js | 59 +++----------------------------------------- js/id/svg/surface.js | 13 +++++++++- 4 files changed, 35 insertions(+), 63 deletions(-) diff --git a/css/map.css b/css/map.css index 756cd11fa..e4dc2fcb0 100644 --- a/css/map.css +++ b/css/map.css @@ -695,17 +695,14 @@ text { opacity: 1; } -.oneway .textpath { - pointer-events: none; - font-size: 7px; - baseline-shift: 2px; - opacity: .7; -} - .oneway .textpath.tag-waterway { fill: #002F35; } +marker#oneway-marker path { + fill:#000; +} + text.tag-oneway { fill:#91CFFF; stroke:#2C6B9B; diff --git a/js/id/svg.js b/js/id/svg.js index 12ffc5c31..c48b9e50d 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -5,6 +5,21 @@ iD.svg = { }; }, + resample: function resample(dx) { + return function() { + var line = d3.svg.line(); + var path = this, + l = path.getTotalLength(), + t = [0], i = 0, dt = dx / l; + while ((i += dt) < 1) t.push(i); + t.push(1); + return line(t.map(function(t) { + var p = path.getPointAtLength(t * l); + return [p.x, p.y]; + })); + }; + }, + PointTransform: function(projection) { return function(entity) { // http://jsperf.com/short-array-join diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 0e91f2ce2..6ba4fb4f1 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -1,8 +1,5 @@ iD.svg.Lines = function(projection) { - var arrowtext = '►\u3000\u3000\u3000', - alength; - var highway_stack = { motorway: 0, motorway_link: 1, @@ -92,16 +89,6 @@ iD.svg.Lines = function(projection) { return paths; } - if (!alength) { - var container = surface.append('g') - .attr('class', 'oneway'), - arrow = container.append('text') - .attr('class', 'textpath') - .text(arrowtext); - alength = arrow.node().getComputedTextLength(); - container.remove(); - } - var lines = []; for (var i = 0; i < entities.length; i++) { @@ -127,47 +114,9 @@ iD.svg.Lines = function(projection) { casings = drawPaths(casing, lines, filter, 'casing', lineString), strokes = drawPaths(stroke, lines, filter, 'stroke', lineString); - // Determine the lengths of oneway paths - var lengths = {}, - oneways = strokes.filter(function(d) { return d.isOneWay(); }).each(function(d) { - lengths[d.id] = Math.floor(this.getTotalLength() / alength); - }).data(); - - var uses = defs.selectAll('path') - .filter(filter) - .data(oneways, iD.Entity.key); - - uses.enter() - .append('path'); - - uses - .attr('id', function(d) { return 'shadow-' + d.id; }) - .attr('d', lineString); - - uses.exit() - .remove(); - - var labels = text.selectAll('text') - .filter(filter) - .data(oneways, iD.Entity.key); - - var tagClasses = iD.svg.TagClasses(); - - var tp = labels.enter() - .append('text') - .attr({ 'class': 'oneway', dy: 4 }) - .append('textPath') - .attr('class', 'textpath') - .call(tagClasses); - - labels.exit().remove(); - - text.selectAll('.textpath') - .filter(filter) - .attr('xlink:href', function(d) { return '#shadow-' + d.id; }) - .text(function(d) { - // adding longer text than necessary, since overflow is hidden - return (new Array(Math.floor(lengths[d.id] * 1.1))).join(arrowtext); - }); + strokes + .filter(function(d) { return d.isOneWay(); }) + .attr('marker-mid', 'url(#oneway-marker)') + .attr('d', iD.svg.resample(60)); }; }; diff --git a/js/id/svg/surface.js b/js/id/svg/surface.js index a11f8fbae..469b9e0d5 100644 --- a/js/id/svg/surface.js +++ b/js/id/svg/surface.js @@ -1,6 +1,17 @@ iD.svg.Surface = function() { return function drawSurface(selection) { - selection.append('defs'); + var defs = selection.append('defs'); + defs.append('marker') + .attr({ + id: 'oneway-marker', + viewBox: '0 0 10 10', + refY: 2.5, + markerWidth: 2, + markerHeight: 2, + orient: 'auto' + }) + .append('path') + .attr('d', 'M 0 0 L 5 2.5 L 0 5 z'); var layers = selection.selectAll('.layer') .data(['fill', 'shadow', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']); From c736f1d312c82006b39c030388fdbd4de5d428b5 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Mar 2013 21:20:09 -0500 Subject: [PATCH 2/5] Fix tests Way lines are no longer the only paths on the map, so we need to be more specific. --- test/spec/svg/areas.js | 4 ++-- test/spec/svg/lines.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js index d84abf8f1..a2ede70e4 100644 --- a/test/spec/svg/areas.js +++ b/test/spec/svg/areas.js @@ -14,8 +14,8 @@ describe("iD.svg.Areas", function () { 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'); + expect(surface.select('path.way')).to.be.classed('way'); + expect(surface.select('path.area')).to.be.classed('area'); }); it("adds tag classes", function () { diff --git a/test/spec/svg/lines.js b/test/spec/svg/lines.js index 0dfa32f67..d38b6f998 100644 --- a/test/spec/svg/lines.js +++ b/test/spec/svg/lines.js @@ -17,8 +17,8 @@ describe("iD.svg.Lines", function () { surface.call(iD.svg.Lines(projection), graph, [line], filter, dimensions); - expect(surface.select('path')).to.be.classed('way'); - expect(surface.select('path')).to.be.classed('line'); + expect(surface.select('path.way')).to.be.classed('way'); + expect(surface.select('path.line')).to.be.classed('line'); }); it("adds tag classes", function () { From fefdecfa67170e910fb5c3becdb8039b68ab203e Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Mar 2013 14:16:08 -0500 Subject: [PATCH 3/5] Corner-preserving resampling --- js/id/svg.js | 48 +++++++++++++++++++++++++++++----------------- js/id/svg/lines.js | 3 ++- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/js/id/svg.js b/js/id/svg.js index c48b9e50d..e819ccf8d 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -5,21 +5,6 @@ iD.svg = { }; }, - resample: function resample(dx) { - return function() { - var line = d3.svg.line(); - var path = this, - l = path.getTotalLength(), - t = [0], i = 0, dt = dx / l; - while ((i += dt) < 1) t.push(i); - t.push(1); - return line(t.map(function(t) { - var p = path.getPointAtLength(t * l); - return [p.x, p.y]; - })); - }; - }, - PointTransform: function(projection) { return function(entity) { // http://jsperf.com/short-array-join @@ -28,8 +13,34 @@ iD.svg = { }; }, - LineString: function(projection, graph, dimensions) { - var cache = {}; + resample: function(points, dx) { + var o = []; + for (var i = 0; i < points.length - 1; i++) { + var a = points[i], b = points[i + 1], + span = iD.geo.dist(a, b); + o.push(a); + // if there is space to fit one or more oneway mark + // in this segment + if (span > dx) { + // the angle from a to b + var angle = Math.atan2(b[1] - a[1], b[0] - a[0]), + to = points[i].slice(); + while (iD.geo.dist(a, to) < (span - dx)) { + // a dx-length line segment in that angle + to[0] += Math.cos(angle) * dx; + to[1] += Math.sin(angle) * dx; + o.push(to.slice()); + } + } + o.push(b); + } + return o; + }, + + LineString: function(projection, graph, dimensions, dx) { + var cache = {}, + resample = this.resample; + return function(entity) { if (cache[entity.id] !== undefined) { return cache[entity.id]; @@ -49,10 +60,11 @@ iD.svg = { cache[entity.id] = segments.map(function(points) { + if (dx) points = resample(points, dx); return 'M' + points.map(function(p) { return p[0] + ',' + p[1]; }).join('L'); - }).join(''); + }.bind(this)).join(''); return cache[entity.id]; }; diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 6ba4fb4f1..354dde8ed 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -104,6 +104,7 @@ iD.svg.Lines = function(projection) { lines.sort(waystack); var lineString = iD.svg.LineString(projection, graph, dimensions); + var lineStringResampled = iD.svg.LineString(projection, graph, dimensions, 20); var shadow = surface.select('.layer-shadow'), casing = surface.select('.layer-casing'), @@ -117,6 +118,6 @@ iD.svg.Lines = function(projection) { strokes .filter(function(d) { return d.isOneWay(); }) .attr('marker-mid', 'url(#oneway-marker)') - .attr('d', iD.svg.resample(60)); + .attr('d', lineStringResampled); }; }; From d083b7ea6ab940041adc392b9f144b3ef1ee2a3a Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Mar 2013 14:17:31 -0500 Subject: [PATCH 4/5] Avoid unnecessary bind --- js/id/svg.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/svg.js b/js/id/svg.js index e819ccf8d..ab9fe8e7e 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -64,7 +64,7 @@ iD.svg = { return 'M' + points.map(function(p) { return p[0] + ',' + p[1]; }).join('L'); - }.bind(this)).join(''); + }).join(''); return cache[entity.id]; }; From ee04bf65f450fce15576972b4906e5885c03d5f4 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Mar 2013 14:37:00 -0500 Subject: [PATCH 5/5] Fine tune marker spacing --- js/id/svg/lines.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 354dde8ed..d34e60fb1 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -104,7 +104,7 @@ iD.svg.Lines = function(projection) { lines.sort(waystack); var lineString = iD.svg.LineString(projection, graph, dimensions); - var lineStringResampled = iD.svg.LineString(projection, graph, dimensions, 20); + var lineStringResampled = iD.svg.LineString(projection, graph, dimensions, 35); var shadow = surface.select('.layer-shadow'), casing = surface.select('.layer-casing'),