Skip to content

Commit 506e552

Browse files
authored
feat: upgrade heatmap layer from mapbox to deckgl (#3372)
* feat: upgrade heatmap layer from mapbox to deckgl Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com> * fixes to shader logic Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com> * fix for noneLayerDataAffectingProps Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com> * remove _unfiltered from layer props Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com> --------- Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>
1 parent 0b975b3 commit 506e552

15 files changed

Lines changed: 376 additions & 269 deletions

File tree

src/components/src/side-panel/layer-panel/layer-configurator.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,13 @@ export default function LayerConfiguratorFactory(
319319
}
320320

321321
_renderHeatmapLayerConfig({layer, visConfiguratorProps, layerChannelConfigProps}) {
322+
const {visConfig} = layer.config;
323+
const aggregationOptions = (layer.visConfigSettings.aggregation?.options || []).map(key => ({
324+
id: key,
325+
label: key.charAt(0) + key.slice(1).toLowerCase()
326+
}));
327+
const selectedAggregation = aggregationOptions.find(({id}) => id === visConfig.aggregation);
328+
322329
return (
323330
<StyledLayerVisualConfigurator>
324331
{/* Color */}
@@ -336,13 +343,35 @@ export default function LayerConfiguratorFactory(
336343
label={false}
337344
/>
338345
</LayerConfigGroup>
346+
{/* Intensity & Threshold */}
347+
<LayerConfigGroup label={'layer.heatmap'}>
348+
<VisConfigSlider {...layer.visConfigSettings.intensity} {...visConfiguratorProps} />
349+
<VisConfigSlider {...layer.visConfigSettings.threshold} {...visConfiguratorProps} />
350+
</LayerConfigGroup>
339351
{/* Weight */}
340352
<LayerConfigGroup label={'layer.weight'}>
341353
<ChannelByValueSelector
342354
channel={layer.visualChannels.weight}
343355
{...layerChannelConfigProps}
344356
/>
345357
</LayerConfigGroup>
358+
{/* Aggregation */}
359+
<LayerConfigGroup label={'layer.aggregation'}>
360+
<SidePanelSection>
361+
<PanelLabel>
362+
<FormattedMessage id={'layerVisConfigs.weightAggregation'} />
363+
</PanelLabel>
364+
<ItemSelector
365+
selectedItems={selectedAggregation}
366+
options={aggregationOptions}
367+
displayOption="label"
368+
getOptionValue="id"
369+
multiSelect={false}
370+
searchable={false}
371+
onChange={value => visConfiguratorProps.onChange({aggregation: value})}
372+
/>
373+
</SidePanelSection>
374+
</LayerConfigGroup>
346375
</StyledLayerVisualConfigurator>
347376
);
348377
}

src/deckgl-layers/src/layer-utils/shader-utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,18 @@ export function editShader(vs: string, type: string, originalText: string, testT
2222

2323
return vs.replace(originalText, testToReplace);
2424
}
25+
26+
export function insertBefore(
27+
vs: string,
28+
type: string,
29+
insertBeforeText: string,
30+
textToInsert: string
31+
) {
32+
const at = vs.indexOf(insertBeforeText);
33+
if (at < 0) {
34+
Console.error(`Cannot edit ${type} layer shader`);
35+
return vs;
36+
}
37+
38+
return vs.slice(0, at) + textToInsert + vs.slice(at);
39+
}

src/layers/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
],
3232
"dependencies": {
3333
"@deck.gl-community/editable-layers": "9.2.8",
34+
"@deck.gl/aggregation-layers": "^9.2.11",
3435
"@deck.gl/core": "^9.2.11",
3536
"@deck.gl/extensions": "^9.2.11",
3637
"@deck.gl/geo-layers": "^9.2.11",
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright contributors to the kepler.gl project
3+
4+
import {HeatmapLayer as DeckGLHeatmapLayer} from '@deck.gl/aggregation-layers';
5+
import {editShader, insertBefore} from '@kepler.gl/deckgl-layers';
6+
7+
/**
8+
* Custom deck.gl HeatmapLayer subclass that patches GPU shaders
9+
* to match the visual appearance of the previous Mapbox GL heatmap layer.
10+
*
11+
* These patches ensure that existing saved maps render identically after
12+
* the migration from Mapbox heatmap to deck.gl heatmap.
13+
*
14+
* Two shader patches are applied:
15+
*
16+
* 1. Weights fragment shader (kernel):
17+
* - Divides Gaussian kernel output by 8.5 and clips small values
18+
* to remove visible hard edges at the radius boundary.
19+
* - Changes the distance input from `2. * dist` to `dist` to
20+
* correctly map the kernel falloff to the configured radius.
21+
*
22+
* 2. Max-weights fragment shader:
23+
* - Forces the red channel to 1.0, because Mapbox assumes
24+
* a max weight of 1.0 when sampling the color ramp.
25+
*/
26+
export default class KeplerHeatmapLayer extends DeckGLHeatmapLayer {
27+
getShaders(shaders: any) {
28+
const result = super.getShaders(shaders);
29+
30+
if (result.fs?.includes('gaussianKDE')) {
31+
// Weights fragment shader: adjust kernel to match Mapbox heatmap layer
32+
let fs = editShader(
33+
result.fs,
34+
'fs',
35+
'return pow(2.71828, -u*u/0.05555)/(1.77245385*0.166666);',
36+
`float value = pow(2.71828, -u*u/0.05555)/(1.77245385*0.166666) / 8.5;
37+
return max(value - 0.00443, 0.0);`
38+
);
39+
fs = editShader(fs, 'fs', '2. * dist', 'dist');
40+
result.fs = fs;
41+
} else if (result.fs?.includes('outTexture.r / max(1.0, outTexture.a)')) {
42+
// Max-weights fragment shader: force max value to 1.0
43+
result.fs = insertBefore(
44+
result.fs,
45+
'fs',
46+
'fragColor.g = outTexture.r / max(1.0, outTexture.a);',
47+
'fragColor.r = 1.0;\n '
48+
);
49+
}
50+
51+
return result;
52+
}
53+
}
54+
55+
KeplerHeatmapLayer.layerName = 'HeatmapLayer';

0 commit comments

Comments
 (0)