Skip to content

Commit

Permalink
text-pitch-alignment (#2668)
Browse files Browse the repository at this point in the history
* First pass at unskewed line labels on pitched maps

* Unskew curved labels as well

* Inherit default value of text-pitch-alignment from text-rotation-alignment.

* Update dependencies
  • Loading branch information
yhahn authored and DAVID CLARK committed Jun 15, 2016
1 parent 626bd9d commit 7467af1
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 35 deletions.
23 changes: 13 additions & 10 deletions js/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ var programAttributes = [{
type: 'Uint8'
}];

function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom) {
function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom, labelangle) {
return array.emplaceBack(
// pos
x,
Expand All @@ -81,7 +81,7 @@ function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom)
tx / 4, // tex
ty / 4, // tex
(labelminzoom || 0) * 10, // labelminzoom
0,
labelangle, // labelangle
// data2
(minzoom || 0) * 10, // minzoom
Math.min(maxzoom || 25, 25) * 10); // minzoom
Expand Down Expand Up @@ -462,11 +462,10 @@ SymbolBucket.prototype.addSymbols = function(programName, quadsStart, quadsEnd,

for (var k = quadsStart; k < quadsEnd; k++) {

var symbol = this.symbolQuadsArray.get(k).SymbolQuad,
angle = symbol.angle;
var symbol = this.symbolQuadsArray.get(k).SymbolQuad;

// drop upside down versions of glyphs
var a = (angle + placementAngle + Math.PI) % (Math.PI * 2);
var a = (symbol.anchorAngle + placementAngle + Math.PI) % (Math.PI * 2);
if (keepUpright && alongLine && (a <= Math.PI / 2 || a > Math.PI * 3 / 2)) continue;

var tl = symbol.tl,
Expand All @@ -484,10 +483,13 @@ SymbolBucket.prototype.addSymbols = function(programName, quadsStart, quadsEnd,
// Lower min zoom so that while fading out the label it can be shown outside of collision-free zoom levels
if (minZoom === placementZoom) minZoom = 0;

var index = addVertex(vertexArray, anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, minZoom, maxZoom, placementZoom);
addVertex(vertexArray, anchorPoint.x, anchorPoint.y, tr.x, tr.y, tex.x + tex.w, tex.y, minZoom, maxZoom, placementZoom);
addVertex(vertexArray, anchorPoint.x, anchorPoint.y, bl.x, bl.y, tex.x, tex.y + tex.h, minZoom, maxZoom, placementZoom);
addVertex(vertexArray, anchorPoint.x, anchorPoint.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, minZoom, maxZoom, placementZoom);
// Encode angle of glyph
var glyphAngle = Math.round((symbol.glyphAngle / (Math.PI * 2)) * 256);

var index = addVertex(vertexArray, anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(vertexArray, anchorPoint.x, anchorPoint.y, tr.x, tr.y, tex.x + tex.w, tex.y, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(vertexArray, anchorPoint.x, anchorPoint.y, bl.x, bl.y, tex.x, tex.y + tex.h, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(vertexArray, anchorPoint.x, anchorPoint.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, minZoom, maxZoom, placementZoom, glyphAngle);

elementArray.emplaceBack(index, index + 1, index + 2);
elementArray.emplaceBack(index + 1, index + 2, index + 3);
Expand Down Expand Up @@ -624,7 +626,8 @@ SymbolBucket.prototype.addSymbolQuad = function(symbolQuad) {
symbolQuad.tex.x,
symbolQuad.tex.y,
//angle
symbolQuad.angle,
symbolQuad.anchorAngle,
symbolQuad.glyphAngle,
// scales
symbolQuad.maxScale,
symbolQuad.minScale);
Expand Down
21 changes: 17 additions & 4 deletions js/render/draw_symbol.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ function drawSymbols(painter, source, layer, coords) {
layer.paint['icon-translate'],
layer.paint['icon-translate-anchor'],
layer.layout['icon-rotation-alignment'],
// icon-pitch-alignment is not yet implemented
// and we simply inherit the rotation alignment
layer.layout['icon-rotation-alignment'],
layer.layout['icon-size'],
layer.paint['icon-halo-width'],
layer.paint['icon-halo-color'],
Expand All @@ -45,6 +48,7 @@ function drawSymbols(painter, source, layer, coords) {
layer.paint['text-translate'],
layer.paint['text-translate-anchor'],
layer.layout['text-rotation-alignment'],
layer.layout['text-pitch-alignment'],
layer.layout['text-size'],
layer.paint['text-halo-width'],
layer.paint['text-halo-color'],
Expand All @@ -61,6 +65,7 @@ function drawLayerSymbols(painter, source, layer, coords, isText,
translate,
translateAnchor,
rotationAlignment,
pitchAlignment,
size,
haloWidth,
haloColor,
Expand All @@ -83,6 +88,7 @@ function drawLayerSymbols(painter, source, layer, coords, isText,
translate,
translateAnchor,
rotationAlignment,
pitchAlignment,
size,
haloWidth,
haloColor,
Expand All @@ -96,21 +102,24 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex
translate,
translateAnchor,
rotationAlignment,
pitchAlignment,
size,
haloWidth,
haloColor,
haloBlur,
opacity,
color) {

var gl = painter.gl;
var tr = painter.transform;
var alignedWithMap = rotationAlignment === 'map';
var rotateWithMap = rotationAlignment === 'map';
var pitchWithMap = pitchAlignment === 'map';

var defaultSize = isText ? 24 : 1;
var fontScale = size / defaultSize;

var extrudeScale, s, gammaScale;
if (alignedWithMap) {
if (pitchWithMap) {
s = pixelsToTileUnits(tile, 1, painter.transform.zoom) * fontScale;
gammaScale = 1 / Math.cos(tr._pitch);
extrudeScale = [s, s];
Expand All @@ -125,7 +134,8 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex

var program = painter.useProgram(sdf ? 'sdf' : 'icon');
gl.uniformMatrix4fv(program.u_matrix, false, painter.translatePosMatrix(posMatrix, tile, translate, translateAnchor));
gl.uniform1i(program.u_skewed, alignedWithMap);
gl.uniform1i(program.u_rotate_with_map, rotateWithMap);
gl.uniform1i(program.u_pitch_with_map, pitchWithMap);
gl.uniform2fv(program.u_extrude_scale, extrudeScale);

gl.activeTexture(gl.TEXTURE0);
Expand All @@ -142,7 +152,7 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex
} else {
var mapMoving = painter.options.rotating || painter.options.zooming;
var iconScaled = fontScale !== 1 || browser.devicePixelRatio !== painter.spriteAtlas.pixelRatio || iconsNeedLinear;
var iconTransformed = alignedWithMap || painter.transform.pitch;
var iconTransformed = pitchWithMap || painter.transform.pitch;
painter.spriteAtlas.bind(gl, sdf || mapMoving || iconScaled || iconTransformed);
gl.uniform2f(program.u_texsize, painter.spriteAtlas.width / 4, painter.spriteAtlas.height / 4);
}
Expand Down Expand Up @@ -181,6 +191,9 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex
gl.uniform4fv(program.u_color, color);
gl.uniform1f(program.u_opacity, opacity);
gl.uniform1f(program.u_buffer, (256 - 64) / 256);
gl.uniform1f(program.u_pitch, tr.pitch / 360 * 2 * Math.PI);
gl.uniform1f(program.u_bearing, tr.bearing / 360 * 2 * Math.PI);
gl.uniform1f(program.u_aspect_ratio, tr.width / tr.height);

for (var i = 0; i < bufferGroups.length; i++) {
group = bufferGroups[i];
Expand Down
3 changes: 3 additions & 0 deletions js/style/style_layer/symbol_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ SymbolStyleLayer.prototype = util.inherit(StyleLayer, {
this.getLayoutValue('symbol-placement', globalProperties, featureProperties) === 'line' &&
!this.getLayoutProperty('icon-rotation-alignment')) {
return 'map';
// If unspecified `text-pitch-alignment` inherits `text-rotation-alignment`
} else if (name === 'text-pitch-alignment' && !this.getLayoutProperty('text-pitch-alignment')) {
return this.getLayoutValue('text-rotation-alignment');
} else {
return StyleLayer.prototype.getLayoutValue.apply(this, arguments);
}
Expand Down
26 changes: 13 additions & 13 deletions js/symbol/quads.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,23 @@ var minScale = 0.5; // underscale by 1 zoom level
* @param {Point} bl The offset of the bottom left corner from the anchor.
* @param {Point} br The offset of the bottom right corner from the anchor.
* @param {Object} tex The texture coordinates.
* @param {number} angle The angle of the label at it's center, not the angle of this quad.
* @param {number} anchorAngle The angle of the label at it's center, not the angle of this quad.
* @param {number} glyphAngle The angle of the glyph to be positioned in the quad.
* @param {number} minScale The minimum scale, relative to the tile's intended scale, that the glyph can be shown at.
* @param {number} maxScale The maximum scale, relative to the tile's intended scale, that the glyph can be shown at.
*
* @class SymbolQuad
* @private
*/
function SymbolQuad(anchorPoint, tl, tr, bl, br, tex, angle, minScale, maxScale) {
function SymbolQuad(anchorPoint, tl, tr, bl, br, tex, anchorAngle, glyphAngle, minScale, maxScale) {
this.anchorPoint = anchorPoint;
this.tl = tl;
this.tr = tr;
this.bl = bl;
this.br = br;
this.tex = tex;
this.angle = angle;
this.anchorAngle = anchorAngle;
this.glyphAngle = glyphAngle;
this.minScale = minScale;
this.maxScale = maxScale;
}
Expand Down Expand Up @@ -88,7 +90,7 @@ function getIconQuads(anchor, shapedIcon, boxScale, line, layout, alongLine) {
br = br.matMult(matrix);
}

return [new SymbolQuad(new Point(anchor.x, anchor.y), tl, tr, bl, br, shapedIcon.image.rect, 0, minScale, Infinity)];
return [new SymbolQuad(new Point(anchor.x, anchor.y), tl, tr, bl, br, shapedIcon.image.rect, 0, 0, minScale, Infinity)];
}

/**
Expand Down Expand Up @@ -155,12 +157,11 @@ function getGlyphQuads(anchor, shaping, boxScale, line, layout, alongLine) {
tl = otl,
tr = otr,
bl = obl,
br = obr,
angle = instance.angle + textRotate;
br = obr;

if (angle) {
var sin = Math.sin(angle),
cos = Math.cos(angle),
if (textRotate) {
var sin = Math.sin(textRotate),
cos = Math.cos(textRotate),
matrix = [cos, -sin, sin, cos];

tl = tl.matMult(matrix);
Expand All @@ -172,9 +173,9 @@ function getGlyphQuads(anchor, shaping, boxScale, line, layout, alongLine) {
// Prevent label from extending past the end of the line
var glyphMinScale = Math.max(instance.minScale, labelMinScale);

var glyphAngle = (anchor.angle + textRotate + instance.offset + 2 * Math.PI) % (2 * Math.PI);
quads.push(new SymbolQuad(instance.anchorPoint, tl, tr, bl, br, rect, glyphAngle, glyphMinScale, instance.maxScale));

var anchorAngle = (anchor.angle + textRotate + instance.offset + 2 * Math.PI) % (2 * Math.PI);
var glyphAngle = (instance.angle + textRotate + instance.offset + 2 * Math.PI) % (2 * Math.PI);
quads.push(new SymbolQuad(instance.anchorPoint, tl, tr, bl, br, rect, anchorAngle, glyphAngle, glyphMinScale, instance.maxScale));
}
}

Expand Down Expand Up @@ -219,7 +220,6 @@ function getSegmentGlyphs(glyphs, anchor, offset, line, segment, forward) {
// Get the angle of the line segment
var angle = Math.atan2(end.y - newAnchorPoint.y, end.x - newAnchorPoint.x);
if (!forward) angle += Math.PI;
if (upsideDown) angle += Math.PI;

glyphs.push({
anchorPoint: newAnchorPoint,
Expand Down
9 changes: 6 additions & 3 deletions js/symbol/symbol_quads.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ var SymbolQuadsArray = module.exports = new StructArrayType({
{ type: 'Int16', name: 'texX' },
{ type: 'Int16', name: 'texY' },

//the angle of the label at it's center, not the angle of this quad.
{ type: 'Float32', name: 'angle' },
// the angle of the label at it's center, not the angle of this quad.
{ type: 'Float32', name: 'anchorAngle' },
// the angle of this quad.
{ type: 'Float32', name: 'glyphAngle' },

// quad is only valid for scales < maxScale && scale > minScale.
{ type: 'Float32', name: 'maxScale' },
Expand All @@ -61,7 +63,8 @@ util.extendAll(SymbolQuadsArray.prototype.StructType.prototype, {
new Point(this.blX, this.blY),
new Point(this.brX, this.brY),
{ x: this.texX, y: this.texY, h: this.texH, w: this.texW, height: this.texH, width: this.texW },
this.angle,
this.anchorAngle,
this.glyphAngle,
this.minScale,
this.maxScale);
}
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
"grid-index": "^0.1.0",
"mapbox-gl-function": "^1.2.1",
"mapbox-gl-js-supported": "^1.1.0",
"mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#f0b94dcc5f782958e9aade61d592a207d8a46e0f",
"mapbox-gl-style-spec": "^8.7.0",
"mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#30caf388dbddd02cfc4f967ffc94c1338c30fbf8",
"mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#2461efc3d883f2f2e56a6c6b2bfd7d54bbfe9f86",
"minifyify": "^7.0.1",
"pbf": "^1.3.2",
"pngjs": "^2.2.0",
Expand Down Expand Up @@ -57,7 +57,7 @@
"highlight.js": "9.3.0",
"istanbul": "^0.4.2",
"lodash": "^4.13.1",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#6d3bcff5d51f6acea41230ffabad6bebdea49fa3",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#5587d796c99145991ea2a7b749a8782b7a0cb483",
"nyc": "^6.1.1",
"remark": "4.2.2",
"remark-html": "3.0.0",
Expand Down
60 changes: 60 additions & 0 deletions test/js/style/style_layer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,66 @@ test('StyleLayer#serialize', function(t) {
t.end();
});

test('StyleLayer#getLayoutValue (default exceptions)', function(assert) {
assert.test('symbol-placement:point => *-rotation-alignment:viewport', function(assert) {
var layer = StyleLayer.create({
"type": "symbol",
"layout": {
"symbol-placement": "point"
}
});
assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'viewport');
assert.equal(layer.getLayoutValue('icon-rotation-alignment'), 'viewport');
assert.end();
});
assert.test('symbol-placement:line => *-rotation-alignment:map', function(assert) {
var layer = StyleLayer.create({
"type": "symbol",
"layout": {
"symbol-placement": "line"
}
});
assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'map');
assert.equal(layer.getLayoutValue('icon-rotation-alignment'), 'map');
assert.end();
});
assert.test('text-rotation-alignment:map => text-pitch-alignment:map', function(assert) {
var layer = StyleLayer.create({
"type": "symbol",
"layout": {
"text-rotation-alignment": "map"
}
});
assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'map');
assert.equal(layer.getLayoutValue('text-pitch-alignment'), 'map');
assert.end();
});
assert.test('text-rotation-alignment:viewport => text-pitch-alignment:viewport', function(assert) {
var layer = StyleLayer.create({
"type": "symbol",
"layout": {
"text-rotation-alignment": "viewport"
}
});
assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'viewport');
assert.equal(layer.getLayoutValue('text-pitch-alignment'), 'viewport');
assert.end();
});
assert.test('text-pitch-alignment respected when set', function(assert) {
var layer = StyleLayer.create({
"type": "symbol",
"layout": {
"text-rotation-alignment": "viewport",
"text-pitch-alignment": "map"
}
});
assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'viewport');
assert.equal(layer.getLayoutValue('text-pitch-alignment'), 'map');
assert.end();
});
assert.end();
});

function createAnimationLoop() {
return {
set: function() {},
Expand Down
6 changes: 4 additions & 2 deletions test/js/symbol/quads.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ test('getIconQuads', function(t) {
bl: { x: -8, y: 5 },
br: { x: 7, y: 5 },
tex: { w: 15, h: 11 },
angle: 0,
anchorAngle: 0,
glyphAngle: 0,
minScale: 0.5,
maxScale: Infinity } ]);
t.end();
Expand All @@ -45,7 +46,8 @@ test('getIconQuads', function(t) {
bl: { x: -8, y: 5 },
br: { x: 7, y: 5 },
tex: { w: 15, h: 11 },
angle: 0,
anchorAngle: 0,
glyphAngle: 0,
minScale: 0.5,
maxScale: Infinity }]);
t.end();
Expand Down

0 comments on commit 7467af1

Please sign in to comment.