简介:这份资源是面向数据分析师、GIS开发者与可视化学习者的Kepler.gl热力图实战教程配套源码包,重点解决空间数据动态展示与时间维度可视化问题。包内共7个文件,约17KB,包含Python脚本、CSV与JSON示例数据、HTML交互页面及依赖说明文件,覆盖从数据导入、图层属性配置到热力图渲染的完整链路。教程以湖北省2020年1—3月疫情数据为案例,演示CSV与JSON数据接入、热力图颜色与半径等参数调节,并加入时间轮播功能,便于观察数据随时间的演变;同时给出Jupyter Notebook中的模块安装与基本用法,帮助读者在交互式环境中复现效果。已有75人学习,适合希望快速掌握Kepler.gl动态热力图配置、并需要可运行代码与样例数据辅助练习的读者参考。
1. 从一份可运行源码说起:Kepler.gl 热力图到底解决什么问题
如果你手上有几十万条带经纬度的点位数据,用普通散点图渲染,浏览器大概率会卡到怀疑人生,而且密集区域会糊成一团黑,根本看不出密度差异。Kepler.gl 的热力图图层就是冲着这个场景来的:它把点聚合成网格或像素级的密度场,用颜色深浅表达聚集程度,既保住了空间分布形态,又把渲染压力压到可接受范围。这份可运行源码的价值在于,它把「数据格式怎么组织、图层参数怎么配、地图底图怎么接」这三件最容易翻车的事一次性跑通了。适合谁?做城市数据分析、门店选址、物流轨迹、设备告警分布的前端和数据工程师,只要你能拿到带经纬度的表格,就能直接改数据复用这套结构。下面我按「先跑通、再调参、再避坑」的顺序拆开讲。
2. 环境搭建与最小可运行工程:从零把热力图跑起来
2.1 依赖选型:为什么用 kepler.gl 而不是自己写 WebGL
常见做法有两种:一是用 deck.gl 自己拼一个 HeatmapLayer,二是直接用 kepler.gl 这套已经封装好的 React 组件。自己写的好处是包体可控,坏处是底图、图层管理、tooltip、图例这些都要自己补,工作量翻倍。kepler.gl 本质上是 deck.gl 的上层封装,热力图能力直接继承自 deck.gl 的 HeatmapLayer,同时白送了一套图层配置 UI 和状态管理。对于「我只想快速验证数据分布」的需求,选 kepler.gl 是更省事的路子。
依赖上核心就三个:kepler.gl、react、react-dom,底图 token 用 Mapbox 的话还需要一个 access token。注意 kepler.gl 对 React 版本有要求,React 18 和部分旧版本 kepler.gl 会有兼容问题,我一般锁 React 18 配较新的 kepler.gl 版本,避免react-dom的 render 方式冲突。
# 初始化工程,用 vite 比 CRA 启动快很多 npm create vite@latest kepler-heatmap-demo -- --template react cd kepler-heatmap-demo # 核心依赖 npm install kepler.gl react-redux redux styled-components # 底图相关,用 mapbox 的话需要 token npm install mapbox-gl安装完先别急着写业务,跑一次npm run dev确认基础工程能起来,再往下加 kepler.gl。这一步能帮你把「是环境问题还是代码问题」提前分开。
2.2 数据格式:热力图对字段的三个硬要求
kepler.gl 的热力图不是随便一张表就能画,它对数据字段有明确要求。最小可用数据集必须包含:
| 字段类型 | 作用 | 是否必需 |
|---|---|---|
| 纬度 latitude | 定位点的南北位置 | 必需 |
| 经度 longitude | 定位点的东西位置 | 必需 |
| 数值字段 | 决定热力权重,没有就按点计数 | 可选但强烈建议 |
如果只有经纬度没有权重字段,热力图会按「每个点权重为 1」来聚合,密集区依然能看出来,但没法表达「这个点本身很重要」。我一般会额外带一个weight字段,比如订单金额、告警次数、停留时长。
数据格式上,kepler.gl 接受 CSV、GeoJSON 和 Pandas DataFrame(Python 侧)。前端直接加载的话,CSV 最省事,字段名用英文,避免中文列名在解析时出玄学问题。
// data/samplePoints.js // 模拟一批带权重的点位数据,实际项目里换成你的 CSV 解析结果 export const samplePoints = [ { lat: 39.9042, lng: 116.4074, weight: 12 }, { lat: 39.9142, lng: 116.4174, weight: 8 }, { lat: 39.8942, lng: 116.3974, weight: 25 }, { lat: 39.9242, lng: 116.4274, weight: 3 }, // ... 实际数据通常几千到几十万条 ];这里lat、lng是定位字段,weight是权重字段。字段名要和后面processCsvData或addDataToMap里配置的字段名完全一致,大小写敏感,这是新手最容易踩的第一个坑。
2.3 最小可运行代码:把热力图图层挂上去
下面这段是能直接跑起来的最小结构,核心是addDataToMap和KeplerGl组件。
// App.jsx import React, { useEffect, useState } from 'react'; import { KeplerGl } from 'kepler.gl'; import { addDataToMap } from 'kepler.gl/actions'; import { useDispatch } from 'react-redux'; import { samplePoints } from './data/samplePoints'; function App() { const dispatch = useDispatch(); const [ready, setReady] = useState(false); useEffect(() => { // 把原始点位转成 kepler.gl 认识的 dataset 结构 const dataset = { info: { id: 'heatmap-demo', label: '热力图示例' }, data: { fields: [ { name: 'lat', type: 'real' }, { name: 'lng', type: 'real' }, { name: 'weight', type: 'integer' }, ], rows: samplePoints.map((p) => [p.lat, p.lng, p.weight]), }, }; dispatch( addDataToMap({ datasets: dataset, option: { centerMap: true, readOnly: false, }, config: { version: 'v1', config: { visState: { layers: [ { id: 'heat-layer', type: 'heatmap', config: { dataId: 'heatmap-demo', label: '密度热力', columns: { lat: 'lat', lng: 'lng' }, isVisible: true, visConfig: { opacity: 0.8, radius: 30, colorRange: { name: 'Global Warming', colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300'], }, }, }, }, ], }, }, }, }) ); setReady(true); }, [dispatch]); return ( <div style={{ width: '100vw', height: '100vh' }}> {ready && <KeplerGl id="map" mapboxApiAccessToken="你的_token" width={window.innerWidth} height={window.innerHeight} />} </div> ); } export default App;逻辑说明:fields定义了三个字段的类型,rows是二维数组,顺序必须和fields一一对应。addDataToMap负责把数据塞进 kepler.gl 的 store,config里的layers才是真正决定热力图长什么样的地方。columns告诉图层哪一列是纬度、哪一列是经度,写错就是一片空白。
参数说明:radius控制热力扩散半径,单位是像素,值越大越糊但越平滑;opacity是图层透明度;colorRange是色带,kepler.gl 内置了多套,也可以自己传colors数组。centerMap: true会让地图自动飞到数据范围,数据量大时这个动作可能卡一下,可以关掉手动定位。
3. 热力图核心参数调优:radius、色带与聚合方式怎么定
3.1 radius 与 opacity:决定「糊」还是「散」的两个旋钮
radius是热力图最敏感的参数。它本质上是每个点的影响半径,deck.gl 的 HeatmapLayer 会在 GPU 里对每个像素累加周围点的贡献。radius 太小,点与点之间断裂,看起来像散点;radius 太大,整个城市糊成一块,密度差异被抹平。
我的经验值:数据点几千条、覆盖一个城市主城区时,radius 从 20 到 40 之间试;数据点几十万条、覆盖全国时,radius 反而要小,10 到 20,否则全国地图上每个省都连成一片。判断标准很简单:调完之后,你关心的几个热点区域应该能明显区分出「核心区」和「边缘区」,如果所有区域颜色差不多,就是 radius 太大。
opacity一般 0.6 到 0.9。太低看不清,太高会盖住底图的路网和地名,失去空间参照。我通常设 0.75 左右,再配合色带的高对比度。
// 调参时可以直接改这段,热更新会立刻反映到地图上 visConfig: { opacity: 0.75, radius: 28, // 色带从冷到热,适合表达密度 colorRange: { name: 'Custom Density', colors: ['#1a9850', '#91cf60', '#d9ef8b', '#fee08b', '#fc8d59', '#d73027'], }, }色带选择上有个反直觉的点:不要用彩虹色。彩虹色在视觉上不是单调递增的,人眼会把中间的绿色误判成「中等偏高」,实际它只是中段。用单色系渐变(浅黄到深红)或者双色系(绿到红)更准确。kepler.gl 内置的Global Warming和Fire都是可用的,自定义时保持色相单调变化。
3.2 聚合方式:点计数还是权重求和
kepler.gl 热力图默认按点计数聚合,也就是每个点贡献相同权重。如果你的数据里有权重字段,需要在图层配置里显式指定。这一步在 UI 上叫「Weight」,在代码里是visConfig.weight或者通过columns里的字段映射。
// 指定权重字段,让热力值反映业务含义而不是单纯的点数 config: { dataId: 'heatmap-demo', label: '加权热力', columns: { lat: 'lat', lng: 'lng' }, visConfig: { radius: 30, opacity: 0.8, // 关键:把 weight 字段作为聚合权重 weight: 'weight', }, }逻辑说明:不指定weight时,一个权重为 100 的点和一个权重为 1 的点在热力图上贡献一样,这显然不符合业务直觉。指定之后,GPU 累加的是权重值,热点会更突出。
参数说明:权重字段必须是数值类型,如果是字符串会静默失败,热力图退化成点计数,而且不报错,这是很隐蔽的坑。加载数据后建议先在 kepler.gl 的字段面板里确认weight被识别成了integer或real。
3.3 底图与坐标系:为什么你的点飘到海里去了
热力图点位偏移,九成是坐标系问题。kepler.gl 默认用 WGS84(EPSG:4326),也就是常见的 GPS 经纬度。如果你拿到的是 GCJ-02(国内地图常见的加密坐标系)或者 Web Mercator 的米制坐标,直接丢进去就会整体偏移几百米甚至几公里。
判断方法:随便挑一个你知道确切位置的点,看它落在底图上的位置对不对。如果整体偏移方向一致,基本就是坐标系没转换。转换可以在数据预处理阶段做,用现成的坐标转换库把 GCJ-02 转成 WGS84,再喂给 kepler.gl。
底图方面,Mapbox 需要 token,没有 token 可以用 kepler.gl 内置的免费底图样式,但样式选择少。生产环境建议自己配 token,并在 kepler.gl 的mapStyle里指定样式 URL。
// 在 KeplerGl 组件上指定底图样式 <KeplerGl id="map" mapboxApiAccessToken="你的_token" mapStyle="mapbox://styles/mapbox/dark-v11" // 深色底图更适合热力图 width={window.innerWidth} height={window.innerHeight} />深色底图配亮色热力是经典组合,对比度高,热点一眼可见。浅色底图适合打印或正式报告,但热力色带要相应调深。
4. 大数据量下的性能与交互:别让热力图把浏览器拖死
4.1 数据分片与按需加载
kepler.gl 虽然用 GPU 渲染,但数据本身还是要进内存。几十万条点位的 JSON 动辄上百 MB,加载和解析阶段就会卡住主线程。常见做法是分片:按时间或按区域切成多个 dataset,初始只加载当前视野范围内的数据,用户缩放或平移时再异步加载。
// 按经纬度范围粗筛,只加载当前视野附近的数据 function filterByBounds(points, bounds) { const { minLng, maxLng, minLat, maxLat } = bounds; return points.filter( (p) => p.lng >= minLng && p.lng <= maxLng && p.lat >= minLat && p.lat <= maxLat ); } // 实际项目里这个筛选应该在服务端做,前端只拿结果 const visiblePoints = filterByBounds(allPoints, currentBounds);逻辑说明:前端全量加载再筛选只是权宜之计,数据量上到百万级必须服务端分片。kepler.gl 支持多次addDataToMap追加 dataset,可以配合地图的onViewportChange事件做懒加载。
参数说明:分片的粒度按业务定,时间序列数据按天或按小时切,空间数据按网格切。切太细请求次数多,切太粗单次加载慢,一般单片的点位控制在 5 万到 10 万条之间比较平衡。
4.2 交互卡顿的排查顺序
热力图卡顿,按这个顺序查:先看数据量,超过 50 万点且没分片,基本就是数据问题;再看 radius,radius 过大时 GPU 每个像素要累加更多点,帧率会掉;最后看是不是同时开了多个图层,热力图叠散点叠轨迹,GPU 压力翻倍。
一个实用技巧:调试阶段先把radius调到很小、opacity调到 0.3,如果这样还卡,说明瓶颈在数据加载或底图,不在热力图层本身。反过来如果这样很流畅,那就是参数问题,逐步调大找到临界点。
提示:kepler.gl 的图层配置面板里可以直接实时改 radius 和 opacity,不用改代码重编译,调参阶段善用这个面板,定下来之后再写回代码。
5. 避坑与常见问题:热力图不显示、偏移、卡顿的排查清单
5.1 地图一片空白,热力图完全不显示
现象:代码跑通,底图出来了,但热力图层什么都没有。
原因:最常见的是columns里的字段名和fields里定义的不一致,或者rows的列顺序和fields对不上。kepler.gl 不会报错,只会静默不渲染。
解决:在addDataToMap之后打开浏览器控制台,检查 store 里的 dataset 字段。或者先把数据量减到 3 条,确认这 3 条能显示,再逐步加量。字段名大小写、下划线都要严格一致。
5.2 点位整体偏移,落在错误的位置
现象:热力图有显示,但位置明显不对,比如应该在北京的点跑到了天津。
原因:坐标系不匹配。GCJ-02 数据直接当 WGS84 用,或者经纬度写反了(lat 和 lng 调换)。
解决:先确认经纬度有没有写反,纬度范围是 -90 到 90,经度是 -180 到 180,如果某个值超出范围就是写反了。再确认坐标系,国内数据源大概率是 GCJ-02,需要转换。转换后再加载,偏移会消失。
5.3 数据量大时浏览器内存暴涨直至崩溃
现象:加载 50 万条以上数据后,页面越来越卡,最后标签页崩溃。
原因:全量数据进内存,且 kepler.gl 内部会保留原始数据和渲染数据两份副本,内存占用是数据体积的两倍以上。
解决:服务端分片,前端按视野加载。另外检查是不是重复调用了addDataToMap,每次调用都会新增 dataset,旧的不清理就会累积。需要更新数据时用updateVisData而不是反复addDataToMap。
5.4 权重字段不生效,热力值还是按点数算
现象:明明带了weight字段,但热力图看起来还是每个点一样重。
原因:权重字段类型不对,或者配置里没指定。字符串类型的权重字段会被忽略。
解决:在fields里把权重字段声明为integer或real,并在图层visConfig里显式指定weight。加载后在字段面板确认类型,如果是string就说明数据源里混了非数字字符,需要清洗。
5.5 热力图颜色和预期相反,密集区反而浅
现象:数据最密集的地方颜色很浅,稀疏区反而深。
原因:色带方向配反了,或者colorRange的 colors 数组顺序和密度映射反了。
解决:kepler.gl 默认把低密度映射到 colors 数组的第一个颜色,高密度映射到最后一个。如果你把深色放在数组开头,就会反过来。调整 colors 顺序,让深色在末尾。
6. 进阶技巧:把热力图从「能看」做到「能决策」
热力图跑通只是起点,真正让它产生决策价值的是叠加分析和动态对比。我常用的一个技巧是「热力图 + 网格聚合」双图层:热力图看整体密度趋势,网格图层看具体数值。kepler.gl 支持同时开多个图层,网格图层用grid类型,把同样的数据按网格聚合,每个网格显示计数或权重和。这样既能看宏观分布,又能点开某个网格看精确值。
// 在 layers 数组里追加一个网格图层,和热力图叠加 { id: 'grid-layer', type: 'grid', config: { dataId: 'heatmap-demo', label: '网格统计', columns: { lat: 'lat', lng: 'lng' }, isVisible: true, visConfig: { opacity: 0.3, colorRange: { name: 'Custom', colors: ['#f7fbff', '#08306b'], }, coverage: 0.8, sizeRange: [10, 50], }, }, }逻辑说明:网格图层的coverage控制网格填充比例,0.8 表示留 20% 间隙,视觉上不会和热力图糊在一起。sizeRange控制网格大小范围,配合地图缩放级别自动调整。
参数说明:两个图层叠加时,热力图放底层、网格放上层,透明度都要降下来,否则互相干扰。我一般热力图 opacity 0.6、网格 0.4,这样两层信息都能读到。
另一个实用技巧是时间轴联动。如果数据带时间字段,kepler.gl 的timeRange过滤器可以让热力图随时间播放,观察热点的迁移。配置时把时间字段声明为timestamp类型,然后在 filter 里加timeRange。播放速度别太快,人眼跟不上,每秒 2 到 3 帧比较合适。
验证热力图是否可信,我有个笨办法但很有效:随机抽 10 个点,手动在地图上找到它们的位置,看热力图的高亮区是不是覆盖了这些点。如果高亮区和实际点位对不上,说明坐标系或聚合逻辑有问题。这个检查花不了几分钟,但能避免拿着错误的热力图去做决策。
最后说个血泪经验:热力图好看,但别用它做精确数值判断。颜色深浅是相对值,不是绝对值,同一个色带在不同数据量下对应的数值完全不同。要做精确对比,一定配合网格图层或 tooltip 看原始数值。我见过有人拿热力图颜色深浅去汇报「A 区域比 B 区域高 30%」,结果一查原始数据只差 8%,颜色感知的误差比想象中大得多。把热力图当趋势工具,把数值判断交给表格和网格,这个习惯能帮你少返工很多次。希望帮到你。
本文还有配套的精品资源,点击获取