Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 48 additions & 12 deletions modules/carto/src/layers/point-label-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,43 @@ class EnhancedTextBackgroundLayer extends TextBackgroundLayer {
}
}

// TextLayer which includes modified text-background-layer-vertex shader and only renders the
// primary background layer in the collision pass
type EnhancedTextLayerProps = TextLayerProps & {
renderBackground?: boolean;
collisionBackgroundPadding?: TextLayerProps['backgroundPadding'];
};

// TextLayer which includes modified text-background-layer-vertex shader and uses a separate
// background layer for collision testing, while preserving TextLayer's visual background support.
class EnhancedTextLayer extends TextLayer {
static layerName = 'EnhancedTextLayer';

filterSubLayer({layer, renderPass}) {
const background = layer.id.includes('primary-background');
const collisionBackground = layer.id.endsWith('-collision-background');
if (renderPass === 'collision') {
return background; // Only draw primary background layer in collision pass
return collisionBackground; // Only draw collision background layer in collision pass
}

return !background; // Do not draw background layer in other passes
return !collisionBackground; // Do not draw collision background layer in other passes
}

renderLayers(): ReturnType<TextLayer['renderLayers']> {
const layers = super.renderLayers();
const backgroundLayer = layers[0] as TextBackgroundLayer | false;
const charactersLayer = layers[1];
const {renderBackground, collisionBackgroundPadding} = this.props as EnhancedTextLayerProps;

const collisionBackgroundLayer =
backgroundLayer &&
new EnhancedTextBackgroundLayer(backgroundLayer.props, {
id: `${this.id}-collision-background`,
padding: collisionBackgroundPadding
});

return [
Boolean(renderBackground) && backgroundLayer,
collisionBackgroundLayer,
charactersLayer
];
}
}

Expand Down Expand Up @@ -154,13 +179,13 @@ export default class PointLabelLayer<
];
}

calculateBackgroundPadding() {
calculateBackgroundPadding(): [number, number, number, number] {
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];
const backgroundPadding: [number, number, number, number] = [0, 0, 0, 0];
if (alignment === 'top') {
backgroundPadding[TOP] = paddingY;
} else if (alignment === 'bottom') {
Expand Down Expand Up @@ -192,6 +217,11 @@ export default class PointLabelLayer<
outlineColor,
outlineWidth,
sizeScale,
getBackgroundColor,
getBorderColor,
getBorderWidth,
backgroundBorderRadius,
backgroundPadding,
radiusScale,

getAlignmentBaseline,
Expand Down Expand Up @@ -223,6 +253,11 @@ export default class PointLabelLayer<
outlineColor,
outlineWidth,
sizeScale,
getBackgroundColor,
getBorderColor,
getBorderWidth,
backgroundBorderRadius,
backgroundPadding,

getAlignmentBaseline,
getColor,
Expand All @@ -242,15 +277,15 @@ export default class PointLabelLayer<
}
}),
{
getSize: 1,
_subLayerProps: {background: {type: EnhancedTextBackgroundLayer}}
getSize: 1
},
props
);
}

renderLayers(): Layer | null | LayersList {
const {
background,
getText,
getSecondaryColor,
getSecondaryText,
Expand All @@ -259,14 +294,15 @@ export default class PointLabelLayer<
updateTriggers
} = this.props;
const getPixelOffset = this.calculatePixelOffset(false);
const backgroundPadding = this.calculateBackgroundPadding();
const collisionBackgroundPadding = this.calculateBackgroundPadding();
const out = [
// Text doesn't update via updateTrigger for some reason
this.renderTextLayer(`${updateTriggers.getText}-primary`, {
backgroundPadding,
background: true,
renderBackground: background,
getText,
getPixelOffset,
background: true // Only use background for primary label for faster collisions
collisionBackgroundPadding
}),
Boolean(getSecondaryText) &&
this.renderTextLayer(`${updateTriggers.getSecondaryText}-secondary`, {
Expand Down
81 changes: 80 additions & 1 deletion test/modules/carto/layers/point-label-layer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test('PointLabelLayer', () => {

expect(
!textLayer.filterSubLayer({layer: textBackgroundLayer, renderPass: 'draw'}),
'background not drawn in draw pass'
'collision background not drawn in draw pass'
).toBeTruthy();
expect(
textLayer.filterSubLayer({layer: multiIconLayer, renderPass: 'draw'}),
Expand All @@ -53,6 +53,85 @@ test('PointLabelLayer', () => {
).toBeTruthy();
}
},
{
props: {
data: FIXTURES.geojson,
background: true,
getBackgroundColor: [255, 255, 255, 200],
getBorderColor: [255, 0, 0, 255],
getBorderWidth: 2,
backgroundBorderRadius: 4,
backgroundPadding: [5, 6, 7, 8]
},
onAfterUpdate: ({subLayers}) => {
const [textLayer] = subLayers;
const textSubLayers = textLayer.getSubLayers();
expect(
textSubLayers.length,
'visual background, collision background and text created'
).toBe(3);

const visualBackgroundLayer = textSubLayers.find(
layer => layer.id.endsWith('-background') && !layer.id.endsWith('-collision-background')
)!;
const collisionBackgroundLayer = textSubLayers.find(layer =>
layer.id.endsWith('-collision-background')
)!;
const multiIconLayer = textSubLayers.find(
layer => layer.constructor.layerName === 'MultiIconLayer'
)!;

expect(visualBackgroundLayer, 'visual background subLayer created').toBeTruthy();
expect(collisionBackgroundLayer, 'collision background subLayer created').toBeTruthy();
expect(multiIconLayer, 'text subLayer created').toBeTruthy();
expect(
visualBackgroundLayer.constructor.layerName,
'visual background uses the standard TextLayer shader'
).toBe('TextBackgroundLayer');
expect(
collisionBackgroundLayer.constructor.layerName,
'collision background uses the expanded collision shader'
).toBe('EnhancedTextBackgroundLayer');

expect(
visualBackgroundLayer.props.padding,
'visual background uses TextLayer padding'
).toEqual([5, 6, 7, 8]);
expect(
visualBackgroundLayer.props.getFillColor,
'visual background color forwarded'
).toEqual([255, 255, 255, 200]);
expect(
visualBackgroundLayer.props.getLineColor,
'visual background border color forwarded'
).toEqual([255, 0, 0, 255]);
expect(
visualBackgroundLayer.props.getLineWidth,
'visual background border width forwarded'
).toBe(2);
expect(
visualBackgroundLayer.props.borderRadius,
'visual background border radius forwarded'
).toBe(4);

expect(
textLayer.filterSubLayer({layer: visualBackgroundLayer, renderPass: 'draw'}),
'visual background drawn in draw pass'
).toBeTruthy();
expect(
!textLayer.filterSubLayer({layer: collisionBackgroundLayer, renderPass: 'draw'}),
'collision background not drawn in draw pass'
).toBeTruthy();
expect(
!textLayer.filterSubLayer({layer: visualBackgroundLayer, renderPass: 'collision'}),
'visual background not drawn in collision pass'
).toBeTruthy();
expect(
textLayer.filterSubLayer({layer: collisionBackgroundLayer, renderPass: 'collision'}),
'collision background drawn in collision pass'
).toBeTruthy();
}
},
{
props: {
data: FIXTURES.geojson,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions test/render/test-cases/text-layer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {TestCase} from '../deck-test-utils';

import {COORDINATE_SYSTEM, OrthographicView} from '@deck.gl/core';
import {TextLayer, PathLayer} from '@deck.gl/layers';
import {PointLabelLayer} from '@deck.gl/carto';
import {PathStyleExtension} from '@deck.gl/extensions';
import {points} from 'deck.gl-test/data';
import fontMapping from '../../data/font-atlas.json';
Expand Down Expand Up @@ -318,6 +319,38 @@ const testCases = [
],
goldenImage: './test/render/golden-images/text-layer-background.png'
},
{
name: 'point-label-layer-background',
viewState: {
target: [0, 0, 0],
zoom: 0
},
views: [new OrthographicView()],
layers: [
new PointLabelLayer({
id: 'point-label',
data: [0],
getPosition: () => [0, 0],
getText: () => 'PointLabelLayer',
getRadius: 0,
sizeScale: 32,
getTextAnchor: 'middle',
getAlignmentBaseline: 'center',
getColor: [180, 0, 0],
background: true,
getBackgroundColor: [240, 250, 255],
getBorderWidth: 3,
getBorderColor: [0, 100, 180],
backgroundPadding: [12, 8],
backgroundBorderRadius: 8,
updateTriggers: {getText: 'point-label'},
_subLayerProps: {
'point-label-primary': {_getFontRenderer: () => fontRenderer}
}
})
],
goldenImage: './test/render/golden-images/point-label-layer-background.png'
},
{
name: 'text-layer-auto-wrapping',
viewState: {
Expand Down