@deck.gl/carto
Version:
CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.
167 lines • 7.64 kB
JavaScript
// 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