UNPKG

mapbox-gl

Version:
283 lines (239 loc) 10.3 kB
'use strict'; var Point = require('point-geometry'); module.exports = { getIconQuads: getIconQuads, getGlyphQuads: getGlyphQuads, SymbolQuad: SymbolQuad }; 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} 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, anchorAngle, glyphAngle, minScale, maxScale) { this.anchorPoint = anchorPoint; this.tl = tl; this.tr = tr; this.bl = bl; this.br = br; this.tex = tex; this.anchorAngle = anchorAngle; this.glyphAngle = glyphAngle; 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 {StyleLayer} layer * @param {boolean} alongLine Whether the icon should be placed along the line. * @param {Shaping} shapedText Shaping for corresponding text * @returns {Array<SymbolQuad>} * @private */ function getIconQuads(anchor, shapedIcon, boxScale, line, layer, alongLine, shapedText, globalProperties, featureProperties) { var rect = shapedIcon.image.rect; var layout = layer.layout; var border = 1; var left = shapedIcon.left - border; var right = left + rect.w / shapedIcon.image.pixelRatio; var top = shapedIcon.top - border; var bottom = top + rect.h / shapedIcon.image.pixelRatio; var tl, tr, br, bl; // text-fit mode if (layout['icon-text-fit'] !== 'none' && shapedText) { var iconWidth = (right - left), iconHeight = (bottom - top), size = layout['text-size'] / 24, textLeft = shapedText.left * size, textRight = shapedText.right * size, textTop = shapedText.top * size, textBottom = shapedText.bottom * size, textWidth = textRight - textLeft, textHeight = textBottom - textTop, padT = layout['icon-text-fit-padding'][0], padR = layout['icon-text-fit-padding'][1], padB = layout['icon-text-fit-padding'][2], padL = layout['icon-text-fit-padding'][3], offsetY = layout['icon-text-fit'] === 'width' ? (textHeight - iconHeight) * 0.5 : 0, offsetX = layout['icon-text-fit'] === 'height' ? (textWidth - iconWidth) * 0.5 : 0, width = layout['icon-text-fit'] === 'width' || layout['icon-text-fit'] === 'both' ? textWidth : iconWidth, height = layout['icon-text-fit'] === 'height' || layout['icon-text-fit'] === 'both' ? textHeight : iconHeight; tl = new Point(textLeft + offsetX - padL, textTop + offsetY - padT); tr = new Point(textLeft + offsetX + padR + width, textTop + offsetY - padT); br = new Point(textLeft + offsetX + padR + width, textTop + offsetY + padB + height); bl = new Point(textLeft + offsetX - padL, textTop + offsetY + padB + height); // Normal icon size mode } else { tl = new Point(left, top); tr = new Point(right, top); br = new Point(right, bottom); bl = new Point(left, bottom); } var angle = layer.getLayoutValue('icon-rotate', globalProperties, featureProperties) * Math.PI / 180; if (alongLine) { var prev = line[anchor.segment]; if (anchor.y === prev.y && anchor.x === prev.x && anchor.segment + 1 < line.length) { var next = line[anchor.segment + 1]; angle += Math.atan2(anchor.y - next.y, anchor.x - next.x) + Math.PI; } else { 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, 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 {StyleLayer} layer * @param {boolean} alongLine Whether the label should be placed along the line. * @returns {Array<SymbolQuad>} * @private */ function getGlyphQuads(anchor, shaping, boxScale, line, layer, alongLine) { var textRotate = layer.layout['text-rotate'] * Math.PI / 180; var keepUpright = layer.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; if (!glyph) continue; 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; if (textRotate) { var sin = Math.sin(textRotate), cos = Math.cos(textRotate), 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 anchorAngle = (anchor.angle + instance.offset + 2 * Math.PI) % (2 * Math.PI); var glyphAngle = (instance.angle + instance.offset + 2 * Math.PI) % (2 * Math.PI); quads.push(new SymbolQuad(instance.anchorPoint, tl, tr, bl, br, rect, anchorAngle, 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; 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; }