UNPKG

@deck.gl/carto

Version:

CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.

167 lines 7.64 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { CompositeLayer, log } from '@deck.gl/core'; import { TextLayer, _TextBackgroundLayer as TextBackgroundLayer } from '@deck.gl/layers'; const [LEFT, TOP, RIGHT, BOTTOM] = [0, 1, 2, 3]; class EnhancedTextBackgroundLayer extends TextBackgroundLayer { getShaders() { const shaders = super.getShaders(); let vs = shaders.vs; // Modify shader so that the padding is offset by the pixel offset to ensure the padding // always captures the anchor point. As padding is uniform we cannot pass it a per-label value vs = vs.replaceAll('textBackground.padding.', '_padding.'); vs = vs.replace('void main(void) {', 'void main(void) {\n vec4 _padding = textBackground.padding + instancePixelOffsets.xyxy * vec4(1.0, 1.0, -1.0, -1.0);'); return { ...shaders, vs }; } } EnhancedTextBackgroundLayer.layerName = 'EnhancedTextBackgroundLayer'; // TextLayer which includes modified text-background-layer-vertex shader and only renders the // primary background layer in the collision pass class EnhancedTextLayer extends TextLayer { filterSubLayer({ layer, renderPass }) { const background = layer.id.includes('primary-background'); if (renderPass === 'collision') { return background; // Only draw primary background layer in collision pass } return !background; // Do not draw background layer in other passes } } EnhancedTextLayer.layerName = 'EnhancedTextLayer'; const defaultProps = { ...TextLayer.defaultProps, getRadius: { type: 'accessor', value: 1 }, radiusScale: { type: 'number', min: 0, value: 1 } }; /** * PointLabelLayer is a layer that renders point labels. * It is a composite layer that renders a primary and secondary label. * It behaves like a TextLayer except that getTextSize is **not supported** * and the text size for the primary label must be set with **sizeScale**. */ class PointLabelLayer extends CompositeLayer { calculatePixelOffset(secondary) { const { getTextAnchor: anchor, getAlignmentBaseline: alignment, getRadius, getSecondaryText, radiusScale, secondarySizeScale, sizeScale } = this.props; const xMult = anchor === 'middle' ? 0 : anchor === 'start' ? 1 : -1; const yMult = alignment === 'center' ? 0 : alignment === 'bottom' ? 1 : -1; // Padding based on font size (font size / 4) const xPadding = sizeScale / 4; const yPadding = sizeScale * (1 + 1 / 4); // Place secondary label under main label (secondary label always 'top' baseline aligned) const secondaryOffset = 0.6 * (1 - yMult) * sizeScale; let yOffset = secondary ? secondaryOffset : 0; // Special case, position relative to secondary label if (anchor === 'middle' && alignment === 'top' && getSecondaryText) { yOffset -= secondaryOffset; yOffset -= secondarySizeScale; yOffset += sizeScale; } // Padding based on point radius (radius/ 4) const radiusPadding = 1 + 1 / 4; return typeof getRadius === 'function' ? (d, info) => { const r = (info ? getRadius(d, info) : 1) * radiusScale * radiusPadding; return [xMult * (r + xPadding), yMult * (r + yPadding) + yOffset]; } : [ xMult * (getRadius * radiusScale * radiusPadding + xPadding), yMult * (getRadius * radiusScale * radiusPadding + yPadding) + yOffset ]; } calculateBackgroundPadding() { const { getTextAnchor: anchor, getAlignmentBaseline: alignment, sizeScale } = this.props; // Heuristics to avoid label overlap const paddingX = 12 * sizeScale; const paddingY = 3 * sizeScale; const backgroundPadding = [0, 0, 0, 0]; if (alignment === 'top') { backgroundPadding[TOP] = paddingY; } else if (alignment === 'bottom') { backgroundPadding[BOTTOM] = paddingY; } else { backgroundPadding[TOP] = 0.5 * paddingY; backgroundPadding[BOTTOM] = 0.5 * paddingY; } if (anchor === 'start') { backgroundPadding[LEFT] = paddingX; } else if (anchor === 'end') { backgroundPadding[RIGHT] = paddingX; } else { backgroundPadding[LEFT] = 0.5 * paddingX; backgroundPadding[RIGHT] = 0.5 * paddingX; } return backgroundPadding; } renderTextLayer(id, { updateTriggers: updateTriggersOverride = {}, ...props }) { const { data, characterSet, fontFamily, fontSettings, fontWeight, outlineColor, outlineWidth, sizeScale, radiusScale, getAlignmentBaseline, getColor, getPosition, getTextAnchor, updateTriggers } = this.props; if (sizeScale < 2) { const propName = this.parent?.props?.textSizeScale ? 'textSizeScale' : 'sizeScale'; log.warn(`${propName} has small value (${sizeScale}). Note getTextSize is not supported on PointLabelLayer`)(); } return new EnhancedTextLayer(this.getSubLayerProps({ id, data, characterSet, fontFamily, fontSettings, fontWeight, outlineColor, outlineWidth, sizeScale, getAlignmentBaseline, getColor, getPosition, getTextAnchor, updateTriggers: { ...updateTriggers, ...updateTriggersOverride, getPixelOffset: [ updateTriggers.getRadius, updateTriggers.getTextAnchor, updateTriggers.getAlignmentBaseline, radiusScale, sizeScale ] } }), { getSize: 1, _subLayerProps: { background: { type: EnhancedTextBackgroundLayer } } }, props); } renderLayers() { const { getText, getSecondaryColor, getSecondaryText, secondaryOutlineColor, secondarySizeScale, updateTriggers } = this.props; const getPixelOffset = this.calculatePixelOffset(false); const backgroundPadding = this.calculateBackgroundPadding(); const out = [ // Text doesn't update via updateTrigger for some reason this.renderTextLayer(`${updateTriggers.getText}-primary`, { backgroundPadding, getText, getPixelOffset, background: true // Only use background for primary label for faster collisions }), Boolean(getSecondaryText) && this.renderTextLayer(`${updateTriggers.getSecondaryText}-secondary`, { getText: getSecondaryText, getPixelOffset: this.calculatePixelOffset(true), getAlignmentBaseline: 'top', // updateTriggers: {getText: updateTriggers.getSecondaryText}, // Optional overrides ...(getSecondaryColor && { getColor: getSecondaryColor }), ...(secondarySizeScale && { sizeScale: secondarySizeScale }), ...(secondaryOutlineColor && { outlineColor: secondaryOutlineColor }) }) ]; return out; } } PointLabelLayer.layerName = 'PointLabelLayer'; PointLabelLayer.defaultProps = defaultProps; export default PointLabelLayer; //# sourceMappingURL=point-label-layer.js.map