mapbox-gl
Version:
A WebGL interactive maps library
676 lines (555 loc) • 27.4 kB
JavaScript
'use strict';
var Point = require('point-geometry');
var Bucket = require('../bucket');
var Anchor = require('../../symbol/anchor');
var getAnchors = require('../../symbol/get_anchors');
var resolveTokens = require('../../util/token');
var Quads = require('../../symbol/quads');
var Shaping = require('../../symbol/shaping');
var resolveText = require('../../symbol/resolve_text');
var mergeLines = require('../../symbol/mergelines');
var clipLine = require('../../symbol/clip_line');
var util = require('../../util/util');
var loadGeometry = require('../load_geometry');
var CollisionFeature = require('../../symbol/collision_feature');
var findPoleOfInaccessibility = require('../../util/find_pole_of_inaccessibility');
var classifyRings = require('../../util/classify_rings');
var shapeText = Shaping.shapeText;
var shapeIcon = Shaping.shapeIcon;
var getGlyphQuads = Quads.getGlyphQuads;
var getIconQuads = Quads.getIconQuads;
var EXTENT = Bucket.EXTENT;
module.exports = SymbolBucket;
function SymbolBucket(options) {
Bucket.apply(this, arguments);
this.showCollisionBoxes = options.showCollisionBoxes;
this.overscaling = options.overscaling;
this.collisionBoxArray = options.collisionBoxArray;
this.symbolQuadsArray = options.symbolQuadsArray;
this.symbolInstancesArray = options.symbolInstancesArray;
this.sdfIcons = options.sdfIcons;
this.iconsNeedLinear = options.iconsNeedLinear;
this.adjustedTextSize = options.adjustedTextSize;
this.adjustedIconSize = options.adjustedIconSize;
this.fontstack = options.fontstack;
}
// this constant is based on the size of the glyphQuadEndIndex and iconQuadEndIndex
// in the symbol_instances StructArrayType
// eg the max valid UInt16 is 65,535
SymbolBucket.MAX_QUADS = 65535;
SymbolBucket.prototype = util.inherit(Bucket, {});
SymbolBucket.prototype.serialize = function() {
var serialized = Bucket.prototype.serialize.apply(this);
serialized.sdfIcons = this.sdfIcons;
serialized.iconsNeedLinear = this.iconsNeedLinear;
serialized.adjustedTextSize = this.adjustedTextSize;
serialized.adjustedIconSize = this.adjustedIconSize;
serialized.fontstack = this.fontstack;
return serialized;
};
var layoutVertexArrayType = new Bucket.VertexArrayType([{
name: 'a_pos',
components: 2,
type: 'Int16'
}, {
name: 'a_offset',
components: 2,
type: 'Int16'
}, {
name: 'a_texture_pos',
components: 2,
type: 'Uint16'
}, {
name: 'a_data',
components: 4,
type: 'Uint8'
}]);
var elementArrayType = new Bucket.ElementArrayType();
function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom, labelangle) {
return array.emplaceBack(
// a_pos
x,
y,
// a_offset
Math.round(ox * 64),
Math.round(oy * 64),
// a_texture_pos
tx / 4, // x coordinate of symbol on glyph atlas texture
ty / 4, // y coordinate of symbol on glyph atlas texture
// a_data
(labelminzoom || 0) * 10, // labelminzoom
labelangle, // labelangle
(minzoom || 0) * 10, // minzoom
Math.min(maxzoom || 25, 25) * 10); // maxzoom
}
SymbolBucket.prototype.addCollisionBoxVertex = function(layoutVertexArray, point, extrude, maxZoom, placementZoom) {
return layoutVertexArray.emplaceBack(
// pos
point.x,
point.y,
// extrude
Math.round(extrude.x),
Math.round(extrude.y),
// data
maxZoom * 10,
placementZoom * 10);
};
SymbolBucket.prototype.programInterfaces = {
glyph: {
layoutVertexArrayType: layoutVertexArrayType,
elementArrayType: elementArrayType
},
icon: {
layoutVertexArrayType: layoutVertexArrayType,
elementArrayType: elementArrayType
},
collisionBox: {
layoutVertexArrayType: new Bucket.VertexArrayType([{
name: 'a_pos',
components: 2,
type: 'Int16'
}, {
name: 'a_extrude',
components: 2,
type: 'Int16'
}, {
name: 'a_data',
components: 2,
type: 'Uint8'
}])
}
};
SymbolBucket.prototype.populateArrays = function(collisionTile, stacks, icons) {
// To reduce the number of labels that jump around when zooming we need
// to use a text-size value that is the same for all zoom levels.
// This calculates text-size at a high zoom level so that all tiles can
// use the same value when calculating anchor positions.
var zoomHistory = { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 };
this.adjustedTextMaxSize = this.layer.getLayoutValue('text-size', {zoom: 18, zoomHistory: zoomHistory});
this.adjustedTextSize = this.layer.getLayoutValue('text-size', {zoom: this.zoom + 1, zoomHistory: zoomHistory});
this.adjustedIconMaxSize = this.layer.getLayoutValue('icon-size', {zoom: 18, zoomHistory: zoomHistory});
this.adjustedIconSize = this.layer.getLayoutValue('icon-size', {zoom: this.zoom + 1, zoomHistory: zoomHistory});
var tileSize = 512 * this.overscaling;
this.tilePixelRatio = EXTENT / tileSize;
this.compareText = {};
this.iconsNeedLinear = false;
this.symbolInstancesStartIndex = this.symbolInstancesArray.length;
var layout = this.layer.layout;
var features = this.features;
var textFeatures = this.textFeatures;
var horizontalAlign = 0.5,
verticalAlign = 0.5;
switch (layout['text-anchor']) {
case 'right':
case 'top-right':
case 'bottom-right':
horizontalAlign = 1;
break;
case 'left':
case 'top-left':
case 'bottom-left':
horizontalAlign = 0;
break;
}
switch (layout['text-anchor']) {
case 'bottom':
case 'bottom-right':
case 'bottom-left':
verticalAlign = 1;
break;
case 'top':
case 'top-right':
case 'top-left':
verticalAlign = 0;
break;
}
var justify = layout['text-justify'] === 'right' ? 1 :
layout['text-justify'] === 'left' ? 0 :
0.5;
var oneEm = 24;
var lineHeight = layout['text-line-height'] * oneEm;
var maxWidth = layout['symbol-placement'] !== 'line' ? layout['text-max-width'] * oneEm : 0;
var spacing = layout['text-letter-spacing'] * oneEm;
var textOffset = [layout['text-offset'][0] * oneEm, layout['text-offset'][1] * oneEm];
var fontstack = this.fontstack = layout['text-font'].join(',');
var geometries = [];
for (var g = 0; g < features.length; g++) {
geometries.push(loadGeometry(features[g]));
}
if (layout['symbol-placement'] === 'line') {
// Merge adjacent lines with the same text to improve labelling.
// It's better to place labels on one long line than on many short segments.
var merged = mergeLines(features, textFeatures, geometries);
geometries = merged.geometries;
features = merged.features;
textFeatures = merged.textFeatures;
}
var shapedText, shapedIcon;
for (var k = 0; k < features.length; k++) {
if (!geometries[k]) continue;
if (textFeatures[k]) {
shapedText = shapeText(textFeatures[k], stacks[fontstack], maxWidth,
lineHeight, horizontalAlign, verticalAlign, justify, spacing, textOffset);
} else {
shapedText = null;
}
if (layout['icon-image']) {
var iconName = resolveTokens(features[k].properties, layout['icon-image']);
var image = icons[iconName];
shapedIcon = shapeIcon(image, layout);
if (image) {
if (this.sdfIcons === undefined) {
this.sdfIcons = image.sdf;
} else if (this.sdfIcons !== image.sdf) {
util.warnOnce('Style sheet warning: Cannot mix SDF and non-SDF icons in one buffer');
}
if (image.pixelRatio !== 1) {
this.iconsNeedLinear = true;
} else if (layout['icon-rotate'] !== 0 || !this.layer.isLayoutValueFeatureConstant('icon-rotate')) {
this.iconsNeedLinear = true;
}
}
} else {
shapedIcon = null;
}
if (shapedText || shapedIcon) {
this.addFeature(geometries[k], shapedText, shapedIcon, features[k]);
}
}
this.symbolInstancesEndIndex = this.symbolInstancesArray.length;
this.placeFeatures(collisionTile, this.showCollisionBoxes);
this.trimArrays();
};
SymbolBucket.prototype.addFeature = function(lines, shapedText, shapedIcon, feature) {
var layout = this.layer.layout;
var glyphSize = 24;
var fontScale = this.adjustedTextSize / glyphSize,
textMaxSize = this.adjustedTextMaxSize !== undefined ? this.adjustedTextMaxSize : this.adjustedTextSize,
textBoxScale = this.tilePixelRatio * fontScale,
textMaxBoxScale = this.tilePixelRatio * textMaxSize / glyphSize,
iconBoxScale = this.tilePixelRatio * this.adjustedIconSize,
symbolMinDistance = this.tilePixelRatio * layout['symbol-spacing'],
avoidEdges = layout['symbol-avoid-edges'],
textPadding = layout['text-padding'] * this.tilePixelRatio,
iconPadding = layout['icon-padding'] * this.tilePixelRatio,
textMaxAngle = layout['text-max-angle'] / 180 * Math.PI,
textAlongLine = layout['text-rotation-alignment'] === 'map' && layout['symbol-placement'] === 'line',
iconAlongLine = layout['icon-rotation-alignment'] === 'map' && layout['symbol-placement'] === 'line',
mayOverlap = layout['text-allow-overlap'] || layout['icon-allow-overlap'] ||
layout['text-ignore-placement'] || layout['icon-ignore-placement'],
symbolPlacement = layout['symbol-placement'],
isLine = symbolPlacement === 'line',
textRepeatDistance = symbolMinDistance / 2;
var list = null;
if (isLine) {
list = clipLine(lines, 0, 0, EXTENT, EXTENT);
} else {
// Only care about looping through the outer rings
list = classifyRings(lines, 0);
}
for (var i = 0; i < list.length; i++) {
var anchors = null;
// At this point it is a list of points for a line or a list of polygon rings
var pointsOrRings = list[i];
var line = null;
// Calculate the anchor points around which you want to place labels
if (isLine) {
line = pointsOrRings;
anchors = getAnchors(
line,
symbolMinDistance,
textMaxAngle,
shapedText,
shapedIcon,
glyphSize,
textMaxBoxScale,
this.overscaling,
EXTENT
);
} else {
line = pointsOrRings[0];
anchors = this.findPolygonAnchors(pointsOrRings);
}
// Here line is a list of points that is either the outer ring of a polygon or just a line
// For each potential label, create the placement features used to check for collisions, and the quads use for rendering.
for (var j = 0, len = anchors.length; j < len; j++) {
var anchor = anchors[j];
if (shapedText && isLine) {
if (this.anchorIsTooClose(shapedText.text, textRepeatDistance, anchor)) {
continue;
}
}
var inside = !(anchor.x < 0 || anchor.x > EXTENT || anchor.y < 0 || anchor.y > EXTENT);
if (avoidEdges && !inside) continue;
// Normally symbol layers are drawn across tile boundaries. Only symbols
// with their anchors within the tile boundaries are added to the buffers
// to prevent symbols from being drawn twice.
//
// Symbols in layers with overlap are sorted in the y direction so that
// symbols lower on the canvas are drawn on top of symbols near the top.
// To preserve this order across tile boundaries these symbols can't
// be drawn across tile boundaries. Instead they need to be included in
// the buffers for both tiles and clipped to tile boundaries at draw time.
var addToBuffers = inside || mayOverlap;
this.addSymbolInstance(anchor, line, shapedText, shapedIcon, this.layer,
addToBuffers, this.symbolInstancesArray.length, this.collisionBoxArray, feature.index, this.sourceLayerIndex, this.index,
textBoxScale, textPadding, textAlongLine,
iconBoxScale, iconPadding, iconAlongLine, {zoom: this.zoom}, feature.properties);
}
}
};
SymbolBucket.prototype.findPolygonAnchors = function(polygonRings) {
var outerRing = polygonRings[0];
if (outerRing.length === 0) {
return [];
} else if (outerRing.length < 3 || !util.isClosedPolygon(outerRing)) {
return [ new Anchor(outerRing[0].x, outerRing[0].y, 0) ];
}
var anchors = null;
// 16 here represents 2 pixels
var poi = findPoleOfInaccessibility(polygonRings, 16);
anchors = [ new Anchor(poi.x, poi.y, 0) ];
return anchors;
};
SymbolBucket.prototype.anchorIsTooClose = function(text, repeatDistance, anchor) {
var compareText = this.compareText;
if (!(text in compareText)) {
compareText[text] = [];
} else {
var otherAnchors = compareText[text];
for (var k = otherAnchors.length - 1; k >= 0; k--) {
if (anchor.dist(otherAnchors[k]) < repeatDistance) {
// If it's within repeatDistance of one anchor, stop looking
return true;
}
}
}
// If anchor is not within repeatDistance of any other anchor, add to array
compareText[text].push(anchor);
return false;
};
SymbolBucket.prototype.placeFeatures = function(collisionTile, showCollisionBoxes) {
this.recalculateStyleLayers();
// Calculate which labels can be shown and when they can be shown and
// create the bufers used for rendering.
this.createArrays();
var layout = this.layer.layout;
var maxScale = collisionTile.maxScale;
var textAlongLine = layout['text-rotation-alignment'] === 'map' && layout['symbol-placement'] === 'line';
var iconAlongLine = layout['icon-rotation-alignment'] === 'map' && layout['symbol-placement'] === 'line';
var mayOverlap = layout['text-allow-overlap'] || layout['icon-allow-overlap'] ||
layout['text-ignore-placement'] || layout['icon-ignore-placement'];
// Sort symbols by their y position on the canvas so that the lower symbols
// are drawn on top of higher symbols.
// Don't sort symbols that won't overlap because it isn't necessary and
// because it causes more labels to pop in and out when rotating.
if (mayOverlap) {
// Only need the symbol instances from the current tile to sort, so convert those instances into an array
// of `StructType`s to enable sorting
var symbolInstancesStructTypeArray = this.symbolInstancesArray.toArray(this.symbolInstancesStartIndex, this.symbolInstancesEndIndex);
var angle = collisionTile.angle;
var sin = Math.sin(angle),
cos = Math.cos(angle);
this.sortedSymbolInstances = symbolInstancesStructTypeArray.sort(function(a, b) {
var aRotated = (sin * a.anchorPointX + cos * a.anchorPointY) | 0;
var bRotated = (sin * b.anchorPointX + cos * b.anchorPointY) | 0;
return (aRotated - bRotated) || (b.index - a.index);
});
}
for (var p = this.symbolInstancesStartIndex; p < this.symbolInstancesEndIndex; p++) {
var symbolInstance = this.sortedSymbolInstances ? this.sortedSymbolInstances[p - this.symbolInstancesStartIndex] : this.symbolInstancesArray.get(p);
var textCollisionFeature = {
boxStartIndex: symbolInstance.textBoxStartIndex,
boxEndIndex: symbolInstance.textBoxEndIndex
};
var iconCollisionFeature = {
boxStartIndex: symbolInstance.iconBoxStartIndex,
boxEndIndex: symbolInstance.iconBoxEndIndex
};
var hasText = !(symbolInstance.textBoxStartIndex === symbolInstance.textBoxEndIndex);
var hasIcon = !(symbolInstance.iconBoxStartIndex === symbolInstance.iconBoxEndIndex);
var iconWithoutText = layout['text-optional'] || !hasText,
textWithoutIcon = layout['icon-optional'] || !hasIcon;
// Calculate the scales at which the text and icon can be placed without collision.
var glyphScale = hasText ?
collisionTile.placeCollisionFeature(textCollisionFeature,
layout['text-allow-overlap'], layout['symbol-avoid-edges']) :
collisionTile.minScale;
var iconScale = hasIcon ?
collisionTile.placeCollisionFeature(iconCollisionFeature,
layout['icon-allow-overlap'], layout['symbol-avoid-edges']) :
collisionTile.minScale;
// Combine the scales for icons and text.
if (!iconWithoutText && !textWithoutIcon) {
iconScale = glyphScale = Math.max(iconScale, glyphScale);
} else if (!textWithoutIcon && glyphScale) {
glyphScale = Math.max(iconScale, glyphScale);
} else if (!iconWithoutText && iconScale) {
iconScale = Math.max(iconScale, glyphScale);
}
// Insert final placement into collision tree and add glyphs/icons to buffers
if (hasText) {
collisionTile.insertCollisionFeature(textCollisionFeature, glyphScale, layout['text-ignore-placement']);
if (glyphScale <= maxScale) {
this.addSymbols('glyph', symbolInstance.glyphQuadStartIndex, symbolInstance.glyphQuadEndIndex, glyphScale, layout['text-keep-upright'], textAlongLine, collisionTile.angle);
}
}
if (hasIcon) {
collisionTile.insertCollisionFeature(iconCollisionFeature, iconScale, layout['icon-ignore-placement']);
if (iconScale <= maxScale) {
this.addSymbols('icon', symbolInstance.iconQuadStartIndex, symbolInstance.iconQuadEndIndex, iconScale, layout['icon-keep-upright'], iconAlongLine, collisionTile.angle);
}
}
}
if (showCollisionBoxes) this.addToDebugBuffers(collisionTile);
};
SymbolBucket.prototype.addSymbols = function(programName, quadsStart, quadsEnd, scale, keepUpright, alongLine, placementAngle) {
var group = this.prepareArrayGroup(programName, 4 * (quadsEnd - quadsStart));
var elementArray = group.elementArray;
var layoutVertexArray = group.layoutVertexArray;
var zoom = this.zoom;
var placementZoom = Math.max(Math.log(scale) / Math.LN2 + zoom, 0);
for (var k = quadsStart; k < quadsEnd; k++) {
var symbol = this.symbolQuadsArray.get(k).SymbolQuad;
// drop upside down versions of glyphs
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,
tr = symbol.tr,
bl = symbol.bl,
br = symbol.br,
tex = symbol.tex,
anchorPoint = symbol.anchorPoint,
minZoom = Math.max(zoom + Math.log(symbol.minScale) / Math.LN2, placementZoom),
maxZoom = Math.min(zoom + Math.log(symbol.maxScale) / Math.LN2, 25);
if (maxZoom <= minZoom) continue;
// 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;
// Encode angle of glyph
var glyphAngle = Math.round((symbol.glyphAngle / (Math.PI * 2)) * 256);
var index = addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, tr.x, tr.y, tex.x + tex.w, tex.y, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, bl.x, bl.y, tex.x, tex.y + tex.h, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(layoutVertexArray, 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);
}
};
SymbolBucket.prototype.updateIcons = function(icons) {
this.recalculateStyleLayers();
var iconValue = this.layer.layout['icon-image'];
if (!iconValue) return;
for (var i = 0; i < this.features.length; i++) {
var iconName = resolveTokens(this.features[i].properties, iconValue);
if (iconName)
icons[iconName] = true;
}
};
SymbolBucket.prototype.updateFont = function(stacks) {
this.recalculateStyleLayers();
var fontName = this.layer.layout['text-font'],
stack = stacks[fontName] = stacks[fontName] || {};
this.textFeatures = resolveText(this.features, this.layer.layout, stack);
};
SymbolBucket.prototype.addToDebugBuffers = function(collisionTile) {
var group = this.prepareArrayGroup('collisionBox', 0);
var layoutVertexArray = group.layoutVertexArray;
var angle = -collisionTile.angle;
var yStretch = collisionTile.yStretch;
for (var j = this.symbolInstancesStartIndex; j < this.symbolInstancesEndIndex; j++) {
var symbolInstance = this.symbolInstancesArray.get(j);
symbolInstance.textCollisionFeature = {boxStartIndex: symbolInstance.textBoxStartIndex, boxEndIndex: symbolInstance.textBoxEndIndex};
symbolInstance.iconCollisionFeature = {boxStartIndex: symbolInstance.iconBoxStartIndex, boxEndIndex: symbolInstance.iconBoxEndIndex};
for (var i = 0; i < 2; i++) {
var feature = symbolInstance[i === 0 ? 'textCollisionFeature' : 'iconCollisionFeature'];
if (!feature) continue;
for (var b = feature.boxStartIndex; b < feature.boxEndIndex; b++) {
var box = this.collisionBoxArray.get(b);
var anchorPoint = box.anchorPoint;
var tl = new Point(box.x1, box.y1 * yStretch)._rotate(angle);
var tr = new Point(box.x2, box.y1 * yStretch)._rotate(angle);
var bl = new Point(box.x1, box.y2 * yStretch)._rotate(angle);
var br = new Point(box.x2, box.y2 * yStretch)._rotate(angle);
var maxZoom = Math.max(0, Math.min(25, this.zoom + Math.log(box.maxScale) / Math.LN2));
var placementZoom = Math.max(0, Math.min(25, this.zoom + Math.log(box.placementScale) / Math.LN2));
this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, tl, maxZoom, placementZoom);
this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, tr, maxZoom, placementZoom);
this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, tr, maxZoom, placementZoom);
this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, br, maxZoom, placementZoom);
this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, br, maxZoom, placementZoom);
this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, bl, maxZoom, placementZoom);
this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, bl, maxZoom, placementZoom);
this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, tl, maxZoom, placementZoom);
}
}
}
};
SymbolBucket.prototype.addSymbolInstance = function(anchor, line, shapedText, shapedIcon, layer, addToBuffers, index, collisionBoxArray, featureIndex, sourceLayerIndex, bucketIndex,
textBoxScale, textPadding, textAlongLine,
iconBoxScale, iconPadding, iconAlongLine, globalProperties, featureProperties) {
var glyphQuadStartIndex, glyphQuadEndIndex, iconQuadStartIndex, iconQuadEndIndex, textCollisionFeature, iconCollisionFeature, glyphQuads, iconQuads;
if (shapedText) {
glyphQuads = addToBuffers ? getGlyphQuads(anchor, shapedText, textBoxScale, line, layer, textAlongLine) : [];
textCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shapedText, textBoxScale, textPadding, textAlongLine, false);
}
glyphQuadStartIndex = this.symbolQuadsArray.length;
if (glyphQuads && glyphQuads.length) {
for (var i = 0; i < glyphQuads.length; i++) {
this.addSymbolQuad(glyphQuads[i]);
}
}
glyphQuadEndIndex = this.symbolQuadsArray.length;
var textBoxStartIndex = textCollisionFeature ? textCollisionFeature.boxStartIndex : this.collisionBoxArray.length;
var textBoxEndIndex = textCollisionFeature ? textCollisionFeature.boxEndIndex : this.collisionBoxArray.length;
if (shapedIcon) {
iconQuads = addToBuffers ? getIconQuads(anchor, shapedIcon, iconBoxScale, line, layer, iconAlongLine, shapedText, globalProperties, featureProperties) : [];
iconCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shapedIcon, iconBoxScale, iconPadding, iconAlongLine, true);
}
iconQuadStartIndex = this.symbolQuadsArray.length;
if (iconQuads && iconQuads.length === 1) {
this.addSymbolQuad(iconQuads[0]);
}
iconQuadEndIndex = this.symbolQuadsArray.length;
var iconBoxStartIndex = iconCollisionFeature ? iconCollisionFeature.boxStartIndex : this.collisionBoxArray.length;
var iconBoxEndIndex = iconCollisionFeature ? iconCollisionFeature.boxEndIndex : this.collisionBoxArray.length;
if (iconQuadEndIndex > SymbolBucket.MAX_QUADS) util.warnOnce("Too many symbols being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907");
if (glyphQuadEndIndex > SymbolBucket.MAX_QUADS) util.warnOnce("Too many glyphs being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907");
return this.symbolInstancesArray.emplaceBack(
textBoxStartIndex,
textBoxEndIndex,
iconBoxStartIndex,
iconBoxEndIndex,
glyphQuadStartIndex,
glyphQuadEndIndex,
iconQuadStartIndex,
iconQuadEndIndex,
anchor.x,
anchor.y,
index);
};
SymbolBucket.prototype.addSymbolQuad = function(symbolQuad) {
return this.symbolQuadsArray.emplaceBack(
// anchorPoints
symbolQuad.anchorPoint.x,
symbolQuad.anchorPoint.y,
// corners
symbolQuad.tl.x,
symbolQuad.tl.y,
symbolQuad.tr.x,
symbolQuad.tr.y,
symbolQuad.bl.x,
symbolQuad.bl.y,
symbolQuad.br.x,
symbolQuad.br.y,
// texture
symbolQuad.tex.h,
symbolQuad.tex.w,
symbolQuad.tex.x,
symbolQuad.tex.y,
//angle
symbolQuad.anchorAngle,
symbolQuad.glyphAngle,
// scales
symbolQuad.maxScale,
symbolQuad.minScale);
};