mapbox-gl
Version:
A WebGL interactive maps library
247 lines (202 loc) • 8 kB
JavaScript
'use strict';
var Point = require('point-geometry');
module.exports = {
getIconQuads: getIconQuads,
getGlyphQuads: getGlyphQuads
};
var minScale = 0.5; // underscale by 1 zoom level
/**
* A textured quad for rendering a single icon or glyph.
*
* The zoom range the glyph can be shown is defined by minScale and maxScale.
*
* @param {Point} anchorPoint the point the symbol is anchored around
* @param {Point} tl The offset of the top left corner from the anchor.
* @param {Point} tr The offset of the top right corner from the anchor.
* @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} 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) {
this.anchorPoint = anchorPoint;
this.tl = tl;
this.tr = tr;
this.bl = bl;
this.br = br;
this.tex = tex;
this.angle = angle;
this.minScale = minScale;
this.maxScale = maxScale;
}
/**
* Create the quads used for rendering an icon.
*
* @param {Anchor} anchor
* @param {PositionedIcon} shapedIcon
* @param {number} boxScale A magic number for converting glyph metric units to geometry units.
* @param {Array<Array<Point>>} line
* @param {LayoutProperties} layout
* @param {boolean} alongLine Whether the icon should be placed along the line.
* @returns {Array<SymbolQuad>}
* @private
*/
function getIconQuads(anchor, shapedIcon, boxScale, line, layout, alongLine) {
var rect = shapedIcon.image.rect;
var border = 1;
var left = shapedIcon.left - border;
var right = left + rect.w;
var top = shapedIcon.top - border;
var bottom = top + rect.h;
var tl = new Point(left, top);
var tr = new Point(right, top);
var br = new Point(right, bottom);
var bl = new Point(left, bottom);
var angle = layout['icon-rotate'] * Math.PI / 180;
if (alongLine) {
var prev = line[anchor.segment];
angle += Math.atan2(anchor.y - prev.y, anchor.x - prev.x);
}
if (angle) {
var sin = Math.sin(angle),
cos = Math.cos(angle),
matrix = [cos, -sin, sin, cos];
tl = tl.matMult(matrix);
tr = tr.matMult(matrix);
bl = bl.matMult(matrix);
br = br.matMult(matrix);
}
return [new SymbolQuad(new Point(anchor.x, anchor.y), tl, tr, bl, br, shapedIcon.image.rect, 0, minScale, Infinity)];
}
/**
* Create the quads used for rendering a text label.
*
* @param {Anchor} anchor
* @param {Shaping} shaping
* @param {number} boxScale A magic number for converting from glyph metric units to geometry units.
* @param {Array<Array<Point>>} line
* @param {LayoutProperties} layout
* @param {boolean} alongLine Whether the label should be placed along the line.
* @returns {Array<SymbolQuad>}
* @private
*/
function getGlyphQuads(anchor, shaping, boxScale, line, layout, alongLine) {
var textRotate = layout['text-rotate'] * Math.PI / 180;
var keepUpright = layout['text-keep-upright'];
var positionedGlyphs = shaping.positionedGlyphs;
var quads = [];
for (var k = 0; k < positionedGlyphs.length; k++) {
var positionedGlyph = positionedGlyphs[k];
var glyph = positionedGlyph.glyph;
var rect = glyph.rect;
if (!rect) continue;
var centerX = (positionedGlyph.x + glyph.advance / 2) * boxScale;
var glyphInstances;
var labelMinScale = minScale;
if (alongLine) {
glyphInstances = [];
labelMinScale = getSegmentGlyphs(glyphInstances, anchor, centerX, line, anchor.segment, true);
if (keepUpright) {
labelMinScale = Math.min(labelMinScale, getSegmentGlyphs(glyphInstances, anchor, centerX, line, anchor.segment, false));
}
} else {
glyphInstances = [{
anchorPoint: new Point(anchor.x, anchor.y),
offset: 0,
angle: 0,
maxScale: Infinity,
minScale: minScale
}];
}
var x1 = positionedGlyph.x + glyph.left,
y1 = positionedGlyph.y - glyph.top,
x2 = x1 + rect.w,
y2 = y1 + rect.h,
otl = new Point(x1, y1),
otr = new Point(x2, y1),
obl = new Point(x1, y2),
obr = new Point(x2, y2);
for (var i = 0; i < glyphInstances.length; i++) {
var instance = glyphInstances[i],
tl = otl,
tr = otr,
bl = obl,
br = obr,
angle = instance.angle + textRotate;
if (angle) {
var sin = Math.sin(angle),
cos = Math.cos(angle),
matrix = [cos, -sin, sin, cos];
tl = tl.matMult(matrix);
tr = tr.matMult(matrix);
bl = bl.matMult(matrix);
br = br.matMult(matrix);
}
// 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));
}
}
return quads;
}
/**
* We can only render glyph quads that slide along a straight line. To draw
* curved lines we need an instance of a glyph for each segment it appears on.
* This creates all the instances of a glyph that are necessary to render a label.
*
* We need a
* @param {Array<Object>} glyphInstances An empty array that glyphInstances are added to.
* @param {Anchor} anchor
* @param {number} offset The glyph's offset from the center of the label.
* @param {Array<Point>} line
* @param {number} segment The index of the segment of the line on which the anchor exists.
* @param {boolean} forward If true get the glyphs that come later on the line, otherwise get the glyphs that come earlier.
*
* @returns {Array<Object>} glyphInstances
* @private
*/
function getSegmentGlyphs(glyphs, anchor, offset, line, segment, forward) {
var upsideDown = !forward;
if (offset < 0) forward = !forward;
if (forward) segment++;
var newAnchorPoint = new Point(anchor.x, anchor.y);
var end = line[segment];
var prevScale = Infinity;
offset = Math.abs(offset);
var placementScale = minScale;
while (true) {
var distance = newAnchorPoint.dist(end);
var scale = offset / distance;
// 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,
offset: upsideDown ? Math.PI : 0,
minScale: scale,
maxScale: prevScale,
angle: (angle + 2 * Math.PI) % (2 * Math.PI)
});
if (scale <= placementScale) break;
newAnchorPoint = end;
// skip duplicate nodes
while (newAnchorPoint.equals(end)) {
segment += forward ? 1 : -1;
end = line[segment];
if (!end) {
return scale;
}
}
var unit = end.sub(newAnchorPoint)._unit();
newAnchorPoint = newAnchorPoint.sub(unit._mult(distance));
prevScale = scale;
}
return placementScale;
}