deck.gl HeatmapLayer 入门实战:基于 React 与 MapLibre 构建 Uber 出行热力图
2026/9/15 21:20:46 网站建设 项目流程

deck.gl HeatmapLayer 入门实战:基于 React 与 MapLibre 构建 Uber 出行热力图

【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl

本指南以仓库中的最小独立示例 examples/website/heatmap 为核心,完整讲解如何在 React + Vite 项目中用@deck.gl/aggregation-layersHeatmapLayer渲染空间分布热力图:从工程搭建、数据格式、图层参数调优,到 GPU 聚合的底层原理与平台限制。读完本文,你将能够独立复制该示例、替换自己的数据集,并针对radiusPixelsintensitythresholdcolorDomain等关键参数做出符合场景的取舍。

示例概览:一个最小可运行的 HeatmapLayer 应用

examples/website/heatmap是 deck.gl 官网上 HeatmapLayer 示例的最小独立版本(README 首句即说明 "This is a minimal standalone version of the HeatmapLayer example")。整个示例只包含 5 个文件:

文件作用
app.tsxReact 组件:组装DeckGLHeatmapLayer与底图
index.htmlHTML 入口,挂载#app根节点并加载 MapLibre CSS
package.json依赖与启动脚本(vite)
tsconfig.jsonTypeScript 编译配置
README.md用法说明(本文所依据的主文档)

示例展示的是纽约市 Uber 打车点位的空间分布热力图,数据来源为 FiveThirtyEight 的公开响应数据,底图由 CARTO 免费底图服务提供。它演示了 heatmap 场景中最典型的组合方式:聚合图层(HeatmapLayer)+ 矢量底图(MapLibre GL)+ React 集成(@deck.gl/react)

快速运行:三步启动示例

按照 README.md 的指引,将该文件夹内容复制到你的项目中,然后执行:

# 安装依赖 npm install # 或使用 yarn yarn # 使用 vite 打包并启动开发服务器 npm start

npm start实际执行的是vite --open(见 package.json),启动后会自动打开浏览器加载应用。Vite 自带热更新,修改app.tsx后页面会即时刷新,非常适合边调参数边看效果。

项目还提供了两个额外脚本:

npm run start-local # 使用仓库根目录的 vite.config.local.mjs,便于在 monorepo 内调试本地源码 npm run build # 执行 vite build,产出生产构建

依赖清单

package.json 中声明的核心依赖如下,版本以当前仓库为准:

  • deck.gl(^9.0.0):聚合包,其中包含@deck.gl/aggregation-layers
  • @deck.gl/react(随 deck.gl 9.x 提供):React 绑定,提供DeckGL组件
  • react/react-dom(^18.0.0):React 运行时
  • react-map-gl(^8.0.0):React 版 MapLibre/Mapbox 封装
  • maplibre-gl(^5.0.0):MapLibre GL 引擎
  • typescript(^4.6.0)与vite(^7.3.3):开发与构建工具

注意:仓库为 monorepo 结构,若想直接引用本地源码进行调试,可运行npm run start-local,它会读取仓库根目录下的 vite.config.local.mjs 完成模块别名解析。

数据格式:position + weight 的点集

HeatmapLayer的输入是带权重的空间点集合。每个数据对象只需提供两个信息:

  1. 位置(position):由getPosition访问器返回,通常为[经度, 经度]的经纬度数组;
  2. 权重(weight):由getWeight访问器返回,表示该点对热力值的贡献量,默认为1

示例使用远程 JSON 数据,见 app.tsx:

const DATA_URL = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/screen-grid/uber-pickup-locations.json';

示例中还通过 TypeScript 类型明确了数据形态(app.tsx):

type DataPoint = [longitude: number, latitude: number, count: number];

即每个元素是一个三元组[经度, 纬度, 上车点数量],其中第三个值正是热力图的权重来源。

接入你自己的数据

更换数据只需做两件事:

  1. 替换dataApp组件的data属性既可接收 URL 字符串(内部自动加载),也可直接传入DataPoint[]数组(见 app.tsx 的类型定义);
  2. 对齐访问器:根据你的数据字段改写getPositiongetWeight,例如:
const layers = [ new HeatmapLayer<MyRecord>({ data: myRecords, getPosition: (d: MyRecord) => d.coordinates, // 返回 [lng, lat] getWeight: (d: MyRecord) => d.value, // 返回数值权重 radiusPixels: 30, intensity: 1, threshold: 0.03 }) ];

关于 HeatmapLayer 全部属性与数据访问器的权威说明,可查阅 docs/api-reference/aggregation-layers/heatmap-layer.md。

核心实现剖析:从入口到图层配置

app.tsx 是示例的完整实现,整体结构如下:

const INITIAL_VIEW_STATE: MapViewState = { longitude: -73.75, // 纽约市中心 latitude: 40.73, zoom: 9, maxZoom: 16, pitch: 0, bearing: 0 }; const MAP_STYLE = 'https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json'; export default function App({ device, data = DATA_URL, intensity = 1, threshold = 0.03, radiusPixels = 30, mapStyle = MAP_STYLE }) { const layers = [ new HeatmapLayer<DataPoint>({ data, id: 'heatmap-layer', pickable: false, getPosition: d => [d[0], d[1]], getWeight: d => d[2], radiusPixels, intensity, threshold }) ]; return ( <DeckGL device={device} initialViewState={INITIAL_VIEW_STATE} controller={true} layers={layers}> <Map reuseMaps mapStyle={mapStyle} /> </DeckGL> ); }

几个值得注意的要点:

  • DeckGL组件:来自@deck.gl/react,通过initialViewState设定初始相机,controller={true}开启平移/缩放交互,layers传入图层实例;
  • Map子组件:来自react-map-gl/maplibrereuseMaps允许 DeckGL 与 MapLibre 共享同一 canvas 渲染管线,mapStyle指定 CARTO 的 dark-matter 无标签样式;
  • device属性@luma.gl/coreDevice类型,可选传入,用于在宿主应用中共享 GPU 设备(示例index.html的挂载脚本会调用renderToDOM完成渲染,见 index.html)。

图层属性精讲:把热力图调到恰到好处

示例将radiusPixelsintensitythreshold暴露为组件的可调参数,下面结合 heatmap-layer.md 与 heatmap-layer.ts 的默认值逐一说明。

radiusPixels:热力晕圈半径

  • 默认值:30(文档)/源码默认50(示例传 30)
  • 含义:单个数据点权重所分布到的圆形区域的像素半径。半径越大,热力点越"晕开",反之越尖锐。
  • 源码约束:{type: 'number', min: 1, max: 100, value: 50},即合法范围 1–100。

intensity:强度缩放

  • 默认值:1
  • 含义:与像素处总权重相乘得到最终权重的乘数。大于 1 会把输出颜色向色谱高端(更热)偏移,小于 1 则向低端偏移。
  • 适合场景:数据整体权重偏低或偏高时,用intensity做全局亮度补偿,示例默认取1

threshold:边缘淡出阈值

  • 默认值:0.05(示例取 0.03)
  • 含义:低权重像素透明度的削减比例。定义为淡出权重与最大权重的比值(0–1),例如0.1表示影响所有权重低于最大值 10% 的像素。threshold越大,色块边界越平滑,但低权重像素因 alpha 过低更难看清。
  • 注意:当指定了colorDomain时,threshold会被忽略

colorDomain:颜色映射域

  • 默认值:null(自动)
  • 含义:[minValue, maxValue]二元组,控制权重如何映射到colorRangeminValue对应colorRange的第一个颜色,maxValue对应最后一个颜色,中间线性插值;低于minValue的像素逐渐淡出至全透明(表示 0),高于maxValue的被截断到最后一个颜色。
  • 聚合模式差异:aggregation: 'SUM'colorDomain每平方米权重解释;'MEAN'时按权重解释。
  • 为何要手动指定:未指定时,最大值由当前视口自动决定,域为[maxValue * threshold, maxValue],因此同一位置的颜色会随视口内其他数据点变化。如需稳定的颜色映射(例如展示图例),必须提供自定义colorDomain

aggregation:聚合操作

  • 默认值:'SUM',可选'SUM'/'MEAN',非法值回退为'SUM'
  • 含义:决定像素颜色值的聚合方式。每个数据点的权重被分配到以该点为中心的圆形区域,像素接收的权重与到中心的距离成反比:
    • 'SUM':落入多个圆圈的像素,权重为所有来源之和;
    • 'MEAN':落入多个圆圈的像素,权重为所有邻近点的加权平均。
  • 源码实现:AGGREGATION_MODE = {SUM: 0, MEAN: 1}(heatmap-layer.ts),在子图层渲染时经aggregationMode传入着色器(heatmap-layer.ts)。

colorRange:色带

  • 默认值:ColorBrewer 的6-class YlOrRd(黄-橙-红渐变)
  • 含义:热力图使用的调色板,形如[color1, color2, ...],每个颜色为[r, g, b, [a]],通道值 0–255,a缺省为 255。颜色数量即色带采样数,中间颜色按权重线性插值。

weightsTextureSize:权重纹理尺寸(性能关键)

  • 默认值:2048,合法范围 128–2048
  • 含义:权重纹理的大小。纹理越小渲染性能越好:官方文档给出的实测参考是 2048×2048 纹理计算最大权重约需 50–100 ms,而 512×512 仅需 5–7 ms;代价是可见的像素化。
  • 源码细节:实际纹理尺寸还会被设备上限裁剪:Math.min(weightsTextureSize, device.limits.maxTextureDimension2D)(heatmap-layer.ts)。

debounceTimeout:交互防抖

  • 默认值:500(毫秒),合法范围 0–1000
  • 含义:视口变化后延迟触发重新聚合的间隔。大数据集配合大radiusPixels时,交互过程中的聚合更新容易造成卡顿;设置正数debounceTimeout可推迟聚合、避免冻结,副作用是交互结束后需要等待片刻才能看到更新结果。
  • 源码实现:_debouncedUpdateWeightmap在 zoom 变化时用setTimeout延迟debounceTimeout后强制更新权重图(heatmap-layer.ts)。

数据访问器:getPosition 与 getWeight

  • getPosition:默认object => object.position,返回每个点的位置(经纬度数组)。
  • getWeight:默认1,返回每个点的权重。不提供时所有点权重相同,热力值只反映点密度。

在示例中,getPosition: d => [d[0], d[1]]从三元组取经纬度,getWeight: d => d[2]取上车点数量,最终热力值即"某区域的上车密度"。

底层原理:GPU 上的高斯核密度估计

HeatmapLayer 与普通图层最大的不同在于聚合发生在 GPU 上。从 heatmap-layer.ts 的源码可以梳理出完整渲染管线:

  1. 权重图生成(weights pass)_createWeightsTransform创建TextureTransform,把每个数据点按其radiusPixelsgetWeight通过weights-vs.glsl/weights-fs.glsl(WebGPU 下为weights.wgsl)绘制到一张权重纹理上,混合模式为加法混合(blendColorOperation: 'add'),从而天然实现多圆叠加求和(heatmap-layer.ts);
  2. 最大权重归约(max pass)maxWeightTransform把权重纹理归约为 1×1 的 max 纹理,采用blendColorOperation: 'max',得到当前视口内的最大权重(heatmap-layer.ts);
  3. 颜色映射(triangle pass)TriangleLayer渲染一个覆盖视口的四边形,片元着色器中结合权重纹理、max 纹理与colorTexture(由colorRange生成的一维纹理,见_updateColorTexture),按intensitythresholdcolorDomain计算最终颜色并叠加到底图上。

视口驱动的优化也体现在源码中:_updateBounds会把当前屏幕四个角unproject成世界坐标,计算出需要处理的可视世界边界,仅对可视范围做聚合(heatmap-layer.ts),这正是colorDomain未指定时颜色随视口变化的原因。

说明:官方文档将 HeatmapLayer 描述为"内部实现高斯核密度估计(Gaussian Kernel Density Estimation)"来渲染热力图,上述源码路径可以印证其 GPU 三趟(weights → max → triangle)的实现结构。

平台限制与 WebGPU 支持

HeatmapLayer 依赖 GPU 浮点纹理渲染,并非所有平台都完整支持。官方文档 heatmap-layer.md 明确列出:

  • WebGPU:使用实例化四边形与 16 位浮点渲染目标,无需依赖 WebGL 点精灵即可保留高斯核密度估计精度;源码中对应rgba16float格式与实例化属性布局(heatmap-layer.ts)。
  • WebGL:在主流桌面浏览器(evergreen)上完整支持;但在iOS Safari上,WebGL 上下文不支持渲染到浮点纹理,图层回退到 8 位低精度模式——此时权重必须是整数,且任意像素累积权重不能超过 255
  • 判定逻辑:源码通过FLOAT_TARGET_FEATURESfloat32-renderable-webgltexture-blend-float-webgl)探测能力,不支持时把纹理格式降级为rgba8unorm并输出警告日志(heatmap-layer.ts)。

因此,如果你的目标平台包含 iOS 移动端,需要留意权重数值范围,或考虑改用 CPU 聚合的图层方案。

更换底图服务

示例默认使用 CARTO 免费底图服务(https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json)。如需替换:

  • 直接修改App组件的mapStyle属性,传入任意 MapLibre Style JSON 的 URL 或内联对象;
  • 更完整的备选方案(Mapbox、MapTiler、自定义瓦片服务等)可参考仓库文档 docs/get-started/using-with-map.md 中关于其他底图服务的说明。

延伸阅读

  • 完整 API 参考:HeatmapLayer 官方文档
  • 图层源码:modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts(含 weights/max/triangle 三个 GPU pass 的 GLSL/WGSL 着色器与工具函数)
  • 聚合图层总览:docs/api-reference/aggregation-layers/overview.md
  • 更多同风格示例:仓库 examples/website 目录下还有 screen-grid、contour、hexagon 等聚合类图层的独立示例,可与本文的 HeatmapLayer 实现相互对照。

【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询