diff --git a/index.html b/index.html index fd25cafe4..0e61eb136 100644 --- a/index.html +++ b/index.html @@ -30,12 +30,21 @@ + + + + + + + + + @@ -58,7 +67,7 @@ - + diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 74b021f90..adad35730 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -15,7 +15,7 @@ iD.Map = function() { background = iD.Background() .projection(projection), transformProp = iD.util.prefixCSSProperty('Transform'), - supersurface, surface, defs, tilegroup, r, g, alength; + supersurface, surface, defs, tilegroup, r, g; function map() { tilegroup = this.append('div') @@ -48,9 +48,6 @@ iD.Map = function() { return (mem[i] = r.append('g').attr('class', 'layer-g layer-' + i)) && mem; }, {}); - var arrow = surface.append('text').text('►----'); - alength = arrow.node().getComputedTextLength(); - arrow.remove(); map.size(this.size()); map.surface = surface; @@ -58,10 +55,6 @@ iD.Map = function() { } function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; } - function getline(d) { return d._line; } - function nodeline(d) { - return 'M' + _.pluck(d.nodes, 'loc').map(projection).map(iD.util.geo.roundCoords).join('L'); - } function drawVector(difference) { if (surface.style(transformProp) != 'none') return; @@ -94,101 +87,12 @@ iD.Map = function() { if (all.length > 10000) return editOff(); else editOn(); - for (var i = 0; i < all.length; i++) { - var entity = all[i]; - switch (entity.geometry()) { - case 'line': - entity._line = nodeline(entity); - ways.push(entity); - lines.push(entity); - break; - - case 'area': - entity._line = nodeline(entity); - ways.push(entity); - areas.push(entity); - break; - - case 'point': - points.push(entity); - break; - - case 'vertex': - vertices.push(entity); - break; - } - } - - var parentStructure = graph.parentStructure(ways); - var wayAccuracyHandles = []; - for (i = 0; i < ways.length; i++) { - accuracyHandles(ways[i], wayAccuracyHandles); - } - - drawVertices(vertices, parentStructure, filter); - drawAccuracyHandles(wayAccuracyHandles, filter); - drawCasings(lines, filter); - drawFills(areas, filter); - drawStrokes(lines, filter); - drawPoints(points, filter); - } - - // updates handles by reference - function accuracyHandles(way, handles) { - for (var i = 0; i < way.nodes.length - 1; i++) { - if (iD.util.geo.dist(way.nodes[i].loc, way.nodes[i + 1].loc) > 0.0001) { - handles.push({ - loc: iD.util.geo.interp(way.nodes[i].loc, way.nodes[i + 1].loc, 0.5), - way: way.id, - index: i + 1, - accuracy: true - }); - } - } - } - - function pointTransform(entity) { - return 'translate(' + iD.util.geo.roundCoords(projection(entity.loc)) + ')'; - } - - function drawVertices(vertices, parentStructure, filter) { - function shared(d) { return parentStructure[d.id] > 1; } - - var circles = g.hit.selectAll('g.vertex') - .filter(filter) - .data(vertices, iD.Entity.key); - - circles.exit().remove(); - - var cg = circles.enter() - .insert('g', ':first-child') - .attr('class', 'node vertex'); - - cg.append('circle') - .attr('class', 'stroke') - .attr('r', 6); - - cg.append('circle') - .attr('class', 'fill') - .attr('r', 4); - - circles.attr('transform', pointTransform) - .classed('shared', shared); - - // Selecting the following implicitly - // sets the data (vertix entity) on the elements - circles.select('circle.fill'); - circles.select('circle.stroke'); - } - - function drawAccuracyHandles(waynodes, filter) { - var handles = g.hit.selectAll('circle.accuracy-handle') - .filter(filter) - .data(waynodes, function (d) { return [d.way, d.index].join(","); }); - handles.exit().remove(); - handles.enter().append('circle') - .attr({ r: 3, 'class': 'accuracy-handle' }); - handles.attr('transform', pointTransform); + surface + .call(iD.svg.Points(), graph, all, filter, projection) + .call(iD.svg.Vertices(), graph, all, filter, projection) + .call(iD.svg.Lines(), graph, all, filter, projection) + .call(iD.svg.Areas(), graph, all, filter, projection) + .call(iD.svg.Midpoints(), graph, all, filter, projection); } function editOff() { @@ -197,94 +101,6 @@ iD.Map = function() { function editOn() { } - function drawLines(data, filter, group, fixedClasses) { - var lines = group.selectAll('path') - .filter(filter) - .data(data, iD.Entity.key); - - lines.exit().remove(); - - lines.enter().append('path') - .attr('class', fixedClasses); - - lines - .order() - .attr('d', getline) - .call(iD.Style.styleClasses()); - - return lines; - } - - function drawFills(areas, filter) { - drawLines(areas, filter, g.fill, 'way area'); - } - - function drawCasings(ways, filter) { - drawLines(ways, filter, g.casing, 'way line casing'); - } - - function drawPoints(points, filter) { - var groups = g.hit.selectAll('g.point') - .filter(filter) - .data(points, iD.Entity.key); - - groups.exit().remove(); - - var group = groups.enter().append('g') - .attr('class', 'node point'); - - group.append('circle') - .attr('class', 'stroke') - .attr({ r: 10 }); - - group.append('circle') - .attr('class', 'fill') - .attr({ r: 10 }); - - group.append('image') - .attr({ width: 16, height: 16 }) - .attr('transform', 'translate(-8, -8)'); - - groups.attr('transform', pointTransform); - - // Selecting the following implicitly - // sets the data (point entity) on the element - groups.select('image').attr('xlink:href', iD.Style.pointImage); - } - - function drawStrokes(ways, filter) { - var strokes = drawLines(ways, filter, g.stroke, 'way line stroke'); - - // 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.exit().remove(); - uses.enter().append('path'); - uses - .attr('id', function(d) { return 'shadow-' + d.id; }) - .attr('d', getline); - - var labels = g.text.selectAll('text') - .filter(filter) - .data(oneways, iD.Entity.key); - labels.exit().remove(); - var tp = labels.enter() - .append('text').attr({ 'class': 'oneway', dy: 4 }) - .append('textPath').attr('class', 'textpath'); - g.text.selectAll('.textpath') - .filter(filter) - .attr('xlink:href', function(d, i) { return '#shadow-' + d.id; }) - .text(function(d) { - return (new Array(Math.floor(lengths[d.id]))).join('►----'); - }); - } - function connectionLoad(err, result) { history.merge(result); redraw(Object.keys(result.entities)); diff --git a/js/id/svg.js b/js/id/svg.js new file mode 100644 index 000000000..fd648f5db --- /dev/null +++ b/js/id/svg.js @@ -0,0 +1,14 @@ +iD.svg = { + RoundProjection: function (projection) { + return function (d) { + return iD.util.geo.roundCoords(projection(d)); + } + }, + + PointTransform: function (projection) { + projection = iD.svg.RoundProjection(projection); + return function (entity) { + return 'translate(' + projection(entity.loc) + ')'; + } + } +}; diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js new file mode 100644 index 000000000..ec77d95c2 --- /dev/null +++ b/js/id/svg/areas.js @@ -0,0 +1,42 @@ +iD.svg.Areas = function() { + return function(surface, graph, entities, filter, projection) { + var areas = []; + + for (var i = 0; i < entities.length; i++) { + var entity = entities[i]; + if (entity.geometry() === 'area') { + areas.push(entity); + } + } + + var lineStrings = {}; + + function lineString(entity) { + return lineStrings[entity.id] || (lineStrings[entity.id] = + 'M' + _.pluck(entity.nodes, 'loc').map(iD.svg.RoundProjection(projection)).join('L')); + } + + function drawPaths(group, areas, filter, classes) { + var paths = group.selectAll('path') + .filter(filter) + .data(areas, iD.Entity.key); + + paths.enter() + .append('path') + .attr('class', classes); + + paths + .order() + .attr('d', lineString) + .call(iD.Style.styleClasses()); + + paths.exit() + .remove(); + + return paths; + } + + var fill = surface.select('.layer-fill'), + paths = drawPaths(fill, areas, filter, 'way area'); + }; +}; diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js new file mode 100644 index 000000000..2f0d6cece --- /dev/null +++ b/js/id/svg/lines.js @@ -0,0 +1,90 @@ +iD.svg.Lines = function() { + return function(surface, graph, entities, filter, projection) { + var arrow = surface.append('text').text('►----'), + alength = arrow.node().getComputedTextLength(); + + arrow.remove(); + + var lines = []; + + for (var i = 0; i < entities.length; i++) { + var entity = entities[i]; + if (entity.geometry() === 'line') { + lines.push(entity); + } + } + + var lineStrings = {}; + + function lineString(entity) { + return lineStrings[entity.id] || (lineStrings[entity.id] = + 'M' + _.pluck(entity.nodes, 'loc').map(iD.svg.RoundProjection(projection)).join('L')); + } + + function drawPaths(group, lines, filter, classes) { + var paths = group.selectAll('path') + .filter(filter) + .data(lines, iD.Entity.key); + + paths.enter() + .append('path') + .attr('class', classes); + + paths + .order() + .attr('d', lineString) + .call(iD.Style.styleClasses()); + + paths.exit() + .remove(); + + return paths; + } + + var casing = surface.select('.layer-casing'), + stroke = surface.select('.layer-stroke'), + defs = surface.select('defs'), + text = surface.select('.layer-text'), + casings = drawPaths(casing, lines, filter, 'way line casing'), + strokes = drawPaths(stroke, lines, filter, 'way line stroke'); + + // 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 tp = labels.enter() + .append('text') + .attr({ 'class': 'oneway', dy: 4 }) + .append('textPath') + .attr('class', 'textpath'); + + labels.exit().remove(); + + text.selectAll('.textpath') + .filter(filter) + .attr('xlink:href', function(d, i) { return '#shadow-' + d.id; }) + .text(function(d) { + return (new Array(Math.floor(lengths[d.id]))).join('►----'); + }); + } +}; diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js new file mode 100644 index 000000000..77bcaa264 --- /dev/null +++ b/js/id/svg/midpoints.js @@ -0,0 +1,36 @@ +iD.svg.Midpoints = function() { + return function(surface, graph, entities, filter, projection) { + var midpoints = []; + + for (var i = 0; i < entities.length; i++) { + var entity = entities[i]; + + if (entity.type !== 'way') + continue; + + for (var j = 0; j < entity.nodes.length - 1; j++) { + if (iD.util.geo.dist(entity.nodes[j].loc, entity.nodes[j + 1].loc) > 0.0001) { + midpoints.push({ + loc: iD.util.geo.interp(entity.nodes[j].loc, entity.nodes[j + 1].loc, 0.5), + way: entity.id, + index: j + 1, + accuracy: true + }); + } + } + } + + var handles = surface.select('.layer-hit').selectAll('circle.accuracy-handle') + .filter(filter) + .data(midpoints, function (d) { return [d.way, d.index].join(","); }); + + handles.enter() + .append('circle') + .attr({ r: 3, 'class': 'accuracy-handle' }); + + handles.attr('transform', iD.svg.PointTransform(projection)); + + handles.exit() + .remove(); + }; +}; diff --git a/js/id/svg/points.js b/js/id/svg/points.js new file mode 100644 index 000000000..a92db711a --- /dev/null +++ b/js/id/svg/points.js @@ -0,0 +1,42 @@ +iD.svg.Points = function() { + return function(surface, graph, entities, filter, projection) { + var points = []; + + for (var i = 0; i < entities.length; i++) { + var entity = entities[i]; + if (entity.geometry() === 'point') { + points.push(entity); + } + } + + var groups = surface.select('.layer-hit').selectAll('g.point') + .filter(filter) + .data(points, iD.Entity.key); + + var group = groups.enter() + .append('g') + .attr('class', 'node point'); + + group.append('circle') + .attr('class', 'stroke') + .attr({ r: 10 }); + + group.append('circle') + .attr('class', 'fill') + .attr({ r: 10 }); + + group.append('image') + .attr({ width: 16, height: 16 }) + .attr('transform', 'translate(-8, -8)'); + + groups.attr('transform', iD.svg.PointTransform(projection)); + + // Selecting the following implicitly + // sets the data (point entity) on the element + groups.select('image') + .attr('xlink:href', iD.Style.pointImage); + + groups.exit() + .remove(); + }; +}; diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js new file mode 100644 index 000000000..f263dc1bf --- /dev/null +++ b/js/id/svg/vertices.js @@ -0,0 +1,41 @@ +iD.svg.Vertices = function() { + return function(surface, graph, entities, filter, projection) { + var vertices = []; + + for (var i = 0; i < entities.length; i++) { + var entity = entities[i]; + if (entity.geometry() === 'vertex') { + vertices.push(entity); + } + } + + var parentStructure = graph.parentStructure(vertices); + + var groups = surface.select('.layer-hit').selectAll('g.vertex') + .filter(filter) + .data(vertices, iD.Entity.key); + + var group = groups.enter() + .insert('g', ':first-child') + .attr('class', 'node vertex'); + + group.append('circle') + .attr('class', 'stroke') + .attr('r', 6); + + group.append('circle') + .attr('class', 'fill') + .attr('r', 4); + + groups.attr('transform', iD.svg.PointTransform(projection)) + .classed('shared', function(d) { return parentStructure[d.id] > 1; }); + + // Selecting the following implicitly + // sets the data (vertix entity) on the elements + groups.select('circle.fill'); + groups.select('circle.stroke'); + + groups.exit() + .remove(); + }; +}; diff --git a/test/index.html b/test/index.html index 5d85f04f7..be9c16b04 100644 --- a/test/index.html +++ b/test/index.html @@ -39,6 +39,14 @@ + + + + + + + +