@deck.gl/carto
Version:
CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.
128 lines (111 loc) • 3.71 kB
JavaScript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
/**
* @filter Heatmap
* @param radiusPixels Blur radius in pixels, controls smoothness of heatmap
* @param colorDomain Domain to apply to values prior to applying color scale
* @param colorTexture 1D RGB lookup texture for color scale
* @param intensity Multiplier to apply to value
* @param opacity Output opacity
*/
const fs = /* glsl */ `\
uniform heatmapUniforms {
vec2 colorDomain;
vec2 delta;
float intensity;
float opacity;
float radiusPixels;
} heatmap;
uniform sampler2D colorTexture;
vec3 colorGradient(float value) {
return texture(colorTexture, vec2(value, 0.5)).rgb;
}
const vec3 SHIFT = vec3(1.0, 256.0, 256.0 * 256.0);
const float MAX_VAL = SHIFT.z * 255.0;
const float SCALE = MAX_VAL / 8.0;
vec4 pack(float value) {
return vec4(mod(vec3(value, floor(value / SHIFT.yz)), 256.0), 255.0) / 255.0;
}
float unpack(vec3 color) {
return 255.0 * dot(color, SHIFT);
}
vec4 heatmap_sampleColor(sampler2D source, vec2 texSize, vec2 texCoord) {
bool firstPass = (heatmap.delta.y < 0.5);
float accumulator = 0.0;
// Controls quality of heatmap, larger values increase quality at expense of performance
float SUPPORT = clamp(heatmap.radiusPixels / 2.0, 8.0, 32.0);
// Gaussian normalization parameters
float sigma = SUPPORT / 3.0;
float a = -0.5 / (sigma * sigma);
float w0 = 0.3989422804014327 / sigma; // 1D normalization
for (float t = -SUPPORT; t <= SUPPORT; t++) {
vec2 percent = (t * heatmap.delta - 0.5) / SUPPORT;
vec2 delta = percent * heatmap.radiusPixels / texSize;
vec4 offsetColor = texture(source, texCoord + delta);
// Unpack float
float value = unpack(offsetColor.rgb);
// Gaussian
float weight = w0 * exp(a * t * t);
accumulator += value * weight;
}
if (firstPass) {
return pack(accumulator);
}
// Undo scaling to obtain normalized density
float density = 10.0 * heatmap.intensity * accumulator / SCALE;
// Domain also in normalized density units
vec2 domain = heatmap.colorDomain;
// Apply domain
float f = (density - domain[0]) / (domain[1] - domain[0]);
// sqrt/log scaling??
// float f = (log(density) - log(domain[0] + 1.0)) / (log(domain[1] + 1.0) - log(domain[0] + 1.0));
// f = sqrt(f);
// Color map
vec4 color = vec4(0.0);
color.rgb = colorGradient(f);
color.a = smoothstep(0.0, 0.1, f);
color.a = pow(color.a, 1.0 / 2.2);
color.a *= heatmap.opacity;
return color;
}
`;
export const heatmap = {
name: 'heatmap',
uniformPropTypes: {
colorDomain: { value: [0, 1] },
delta: { value: [0, 1] },
intensity: { value: 1, min: 0.1, max: 10 },
opacity: { value: 1, min: 0, max: 1 },
radiusPixels: { value: 20, min: 0, softMax: 100 }
},
uniformTypes: {
colorDomain: 'vec2<f32>',
delta: 'vec2<f32>',
intensity: 'f32',
opacity: 'f32',
radiusPixels: 'f32'
},
// @ts-ignore TODO v9.1
getUniforms: opts => {
if (!opts)
return {};
const { colorDomain = [0, 1], colorTexture, delta = [1, 0], intensity = 1, opacity = 1, radiusPixels = 20 } = opts;
return {
colorDomain,
colorTexture,
delta,
intensity,
opacity,
radiusPixels
};
},
fs,
passes: [
// @ts-expect-error Seems typing in luma.gl should be Partial<>
{ sampler: true, uniforms: { delta: [1, 0] } },
// @ts-expect-error Seems typing in luma.gl should be Partial<>
{ sampler: true, uniforms: { delta: [0, 1] } }
]
};
//# sourceMappingURL=heatmap.js.map