From 9e784b6bc958fe7ffa2f968954e51163a8ed8718 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 18 Jan 2013 16:34:41 -0500 Subject: [PATCH] mostly working path and point labelling --- css/map.css | 6 +- js/id/svg/labels.js | 191 ++++++++++++++++++++++++++++++++++++++------ js/id/svg/lines.js | 9 ++- 3 files changed, 179 insertions(+), 27 deletions(-) diff --git a/css/map.css b/css/map.css index af1ea9226..9d09b1b63 100644 --- a/css/map.css +++ b/css/map.css @@ -284,7 +284,11 @@ text.tag-oneway { pointer-events:none; } -text.label { +text.textpath-label { + text-anchor: middle; +} + +text.textpath-label, text.text-label { font-size: 12px; font-weight: bold; fill: black; diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index cf1d484d8..317c0648e 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -1,12 +1,47 @@ iD.svg.Labels = function() { var pointOffsets = [ - [15, 3, 'start'], // right + [25, 3, 'start'], // right [-15, 3, 'end'], // left ]; + var height = 12, + width = 6; + + function drawTextPaths(group, labels, filter, classes, t) { + + var reverse = t('reverse'); + + var texts = group.selectAll('text.textpath-label') + .data(labels); + + var tp = texts.enter() + .append('text') + .attr({ 'class': classes}) + .append('textPath') + .attr({ + 'class': 'textpath', + 'startOffset': '50%' + }); + + var tps = group.selectAll('.textpath-label .textpath') + .data(labels) + .attr({ + 'xlink:href': function(d, i) { return '#casing-' + d.id}, + 'glyph-orientation-vertical': function(d, i) {return reverse(d, i) ? 180 : 0}, + 'glyph-orientation-horizontal': function(d, i) {return reverse(d, i) ? 180 : 0}, + 'dominant-baseline': 'central' + }) + .text(function(d, i) { + return reverse(d, i) ? d.tags.name.split('').reverse().join('') : d.tags.name; + }); + + texts.exit().remove(); + + } + function drawTexts(group, labels, filter, classes, position) { - var texts = group.selectAll('text') + var texts = group.selectAll('text.text-label') .filter(filter) .data(labels); @@ -24,42 +59,152 @@ iD.svg.Labels = function() { return texts; } + function getPathTransform(projection) { + var nodeCache = {}; + return function pathTransform(entity) { + var length = 0, + w = entity.tags.name.length * width; + + var nodes = nodeCache[entity.id]; + if (typeof nodes === 'undefined') { + nodes = nodeCache[entity.id] = _.pluck(entity.nodes, 'loc') + .map(iD.svg.RoundProjection(projection)); + } + + function segmentLength(i) { + var dx = nodes[i][0] - nodes[i + 1][0]; + var dy = nodes[i][1] - nodes[i + 1][1]; + return Math.sqrt(dx * dx + dy * dy); + } + + for (var i = 0; i < nodes.length - 1; i++) { + length += segmentLength(i); + } + + if (length < w + 20) return null; + + var ends = (length - w) / 2, + sofar = 0, + start, end, + n1, n2; + + for (var i = 0; i < nodes.length - 1; i++) { + var current = segmentLength(i); + if (!start && sofar + current > ends) { + var portion = (ends - sofar) / current; + start = [ + nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]), + nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1]) + ]; + n1 = nodes[i + 1]; + } + if (!end && sofar + current > length - ends) { + var portion = (length - ends - sofar) / current; + end = [ + nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]), + nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1]) + ]; + n2 = nodes[i]; + } + sofar += current; + } + + var angle = Math.atan2(n1[1] - start[1], n1[0] - start[0]); + var reverse = !(start[0] < end[0] && angle < Math.PI/2 && angle > - Math.PI/2); + + return { + length: length, + width: Math.abs(start[0] - end[0]), + height: Math.abs(start[1] - end[1]), + start: start, + end: end, + reverse: reverse + }; + } + } + + return function drawLabels(surface, graph, entities, filter, projection) { - var points = []; + var project = iD.svg.RoundProjection(projection); + var rtree = new RTree(); + + function addPoint(d, i) { + var bbox = this.getBBox(); + var coords = project(d.loc); + var tree = new RTree.Rectangle(coords[0], coords[1], bbox.width, bbox.height); + rtree.insert(tree, d.id); + } + + //d3.selectAll('.node.point').each(addPoint); + + var points = [], + roads = []; for (var i = 0; i < entities.length; i++) { var entity = entities[i]; - if (entity.geometry() === 'point' && entity.tags.name) { + if (entity.geometry() === 'line' && entity.tags.highway && entity.tags.name) { + roads.push(entity); + } else if (entity.geometry() === 'point' && entity.tags.name) { points.push(entity); } } - var labels = points; - var positions = []; + var entities = roads.concat(points); + var rect; + var pathTransform = getPathTransform(projection); + var textlabels = [], + pathlabels = [], + textpositions = [], + pathpositions = []; - var project = iD.svg.RoundProjection(projection); - - for (var i = 0; i < labels.length; i ++) { - positions[i] = {}; - var l = labels[i]; - if (l.type === 'node') { - var coord = project(l.loc), - offset = pointOffsets[1]; - positions[i].x = coord[0] + offset[0]; - positions[i].y = coord[1] + offset[1]; - positions[i].textAnchor = offset[2]; - } + function nocollisions(rect) { + var v = rtree.search(rect, true).length === 0; + if (v) rtree.insert(rect); + return v; } - function position(attr) { - return function(d, i) { return positions[i][attr] }; + for (var i = 0; i < entities.length; i ++) { + var p = {}, + entity = entities[i], + w = width * entity.tags.name.length, + h = 20; + + if (entity.type === 'node') { + var coord = project(entity.loc), + offset = pointOffsets[0]; + p.x = coord[0] + offset[0]; + p.y = coord[1] + offset[1]; + p.textAnchor = offset[2]; + rect = new RTree.Rectangle(p.x, p.y, width * entity.tags.name.length, 20); + if (nocollisions(rect)) { + textpositions.push(p); + textlabels.push(entity); + } + + } else if (entity.type === 'way' && entity.geometry() === 'line') { + p = pathTransform(entity); + if (!p) continue; + rect = new RTree.Rectangle(Math.min(p.start[0], p.end[0]) - 5, Math.min(p.start[1], p.end[1]) - 5, p.width + 10, p.height + 10); + if (nocollisions(rect)) { + pathpositions.push(p); + pathlabels.push(entity); + } + } + + } + + + function textposition(attr) { + return function(d, i) { return textpositions[i][attr] }; + } + function pathposition(attr) { + return function(d, i) { return pathpositions[i][attr] }; } var label = surface.select('.layer-label'), - texts = drawTexts(label, labels, filter, 'label', position); - - + texts = drawTexts(label, textlabels, filter, 'text-label', textposition), + textPaths = drawTextPaths(label, pathlabels, filter, 'textpath-label', pathposition); }; }; diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 3f1b634d7..48eef2a26 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -33,13 +33,16 @@ iD.svg.Lines = function() { return as - bs; } - function drawPaths(group, lines, filter, classes, lineString) { + function drawPaths(group, lines, filter, classes, lineString, prefix) { var paths = group.selectAll('path') .filter(filter) .data(lines, iD.Entity.key); paths.enter() .append('path') + .attr('id', function(d) { + return prefix + d.id; + }) .attr('class', classes); paths @@ -87,8 +90,8 @@ iD.svg.Lines = function() { stroke = surface.select('.layer-stroke'), defs = surface.select('defs'), text = surface.select('.layer-text'), - casings = drawPaths(casing, lines, filter, 'way line casing', lineString), - strokes = drawPaths(stroke, lines, filter, 'way line stroke', lineString); + casings = drawPaths(casing, lines, filter, 'way line casing', lineString, 'casing-'), + strokes = drawPaths(stroke, lines, filter, 'way line stroke', lineString, 'stroke-'); // Determine the lengths of oneway paths var lengths = {},