☰
Kepler.gl 热力图教程:从 CSV 到可交互地图的完整源码实践
2026/10/18 16:26:36 网站建设 项目流程

简介:这份资源是面向数据分析师、GIS开发者与可视化爱好者的Kepler.gl动态热力图实战教程配套源码包,帮助读者快速掌握从数据导入到时间轮播效果呈现的完整流程。压缩包共7个文件,约17KB,包含Python脚本、CSV与JSON示例数据、HTML可视化页面及依赖说明文件,覆盖数据准备、图层配置与渲染实现等环节。教程以湖北省2020年1至3月疫情数据为案例,演示热力图颜色、半径、强度等属性设置,并给出Jupyter Notebook中的模块安装与基本用法,同时附带多色阶配置器页面,便于调整配色方案。目前已有75人学习下载,适合希望将空间数据转化为直观可视化结果、提升疫情传播与人口流动分析效率的开发者参考实践。

1. Kepler.gl 热力图教程:从 CSV 到可交互地图的完整路径

手里有一份带经纬度的 CSV,想快速看出数据在空间上的聚集规律,最省事的做法不是写前端地图代码,而是把文件拖进 Kepler.gl,调两下参数,一张可交互的热力图就出来了。Kepler.gl 是 Uber 开源的地理空间可视化工具,热力图是它内置图层类型里使用频率最高的一种,适合做城市事件分布、门店客流密度、设备上报点位这类「点数据看聚集」的场景。这篇教程围绕「可运行源码」这个关键词展开,我会把一份最小可跑的 HTML 工程拆开讲清楚:数据怎么准备、图层怎么配、参数怎么调、导出怎么选,以及我在实际项目里踩过的几个坑。读者不需要有 GIS 背景,会写一点 JavaScript、能起一个本地静态服务就能跟着做。

2. 热力图的数据准备与 Kepler.gl 接入方式

2.1 点数据的最小字段要求

Kepler.gl 的热力图图层吃的是点数据,每个点至少要有经度和纬度两个字段。字段名不强制叫lat/lng,但列名里带lat、latitude、纬度这类关键词时,Kepler.gl 在自动识别字段类型阶段会更容易命中。我一般会把字段名统一成latitude和longitude,避免中文列名在 CSV 编码上出问题。

除了经纬度,热力图真正决定视觉密度的是「权重」。如果你的数据是原始点位(一个事件一行),那权重就是 1,Kepler.gl 会按点的堆叠密度渲染;如果数据已经做过聚合(比如每个网格有一个count),那就把count作为权重字段传进去,热力值会按这个字段加权。这一点很多人第一次用会忽略,导致明明有聚合值,图上却还是按点数算。

一个典型的最小 CSV 长这样:

latitude,longitude,weight,event_type 31.2304,121.4737,3,delivery 31.2350,121.4800,1,delivery 31.2200,121.4600,5,repair

字段类型上,经纬度必须是数值,权重也必须是数值。如果 CSV 里混了空字符串或N/A,Kepler.gl 加载后这些行会被丢掉,但不会报错,只会让你觉得「怎么少了一批点」。所以加载前先做一次清洗,把空经纬度行过滤掉。

2.2 三种接入方式怎么选

Kepler.gl 的接入方式常见有三种,选哪种取决于你要交付什么。

第一种是纯在线版,打开网页拖文件,适合一次性探索。缺点是数据要上传到对方服务器,内部数据基本不能这么干。

第二种是 Jupyter Notebook 里用keplergl这个 Python 包,适合数据分析流程里顺手出图,数据不出本地。安装命令是pip install keplergl,然后在 notebook 里from keplergl import KeplerGl,把 DataFrame 传进去即可。这种方式适合做分析报告,但不适合交付给非技术同事。

第三种是前端工程里嵌入,也就是这篇教程重点讲的「可运行源码」路线。用kepler.gl的 React 组件或者@kepler.gl/components自己搭一个页面,数据从本地文件或接口读,整个地图跑在自己的服务里。适合要做成内部工具、需要反复用同一套配置看不同批次数据的场景。

我一般会先在线版快速验证字段和图层效果,确认没问题后再把配置导出成 JSON,嵌到前端工程里复用。这样省去在代码里反复试参数的时间。

2.3 用 Python 快速生成一份可用的测试数据

在动手搭前端之前,先造一份有聚集特征的数据,不然热力图出来是一片均匀的点,看不出效果。下面这段脚本生成三个聚集中心,每个中心周围撒点,并给不同中心不同权重。

import csv import random # 三个虚拟聚集中心,仅用于演示,非真实地点 centers = [ (31.23, 121.47, 5), # 中心A,权重高 (31.26, 121.50, 2), # 中心B,权重中 (31.20, 121.44, 1), # 中心C,权重低 ] rows = [] for lat_c, lng_c, w in centers: for _ in range(300): # 在中心附近做正态抖动,形成聚集 lat = lat_c + random.gauss(0, 0.01) lng = lng_c + random.gauss(0, 0.01) rows.append([round(lat, 6), round(lng, 6), w, "demo"]) with open("points.csv", "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["latitude", "longitude", "weight", "event_type"]) writer.writerows(rows) print("generated", len(rows), "rows")

这段代码的关键点是random.gauss(0, 0.01),标准差 0.01 度大约对应一公里左右的散布范围,三个中心之间距离足够远,热力图能明显看出三团。权重字段w让三个中心的颜色深浅不同,方便验证权重参数是否生效。生成后打开 CSV 确认行数和列名,再进入下一步。

3. 用可运行源码搭一个最小热力图页面

3.1 工程结构与依赖安装

这一节给出一份最小可运行的前端工程。目录结构如下:

kepler-heat-demo/ index.html main.js points.csv package.json

依赖只需要kepler.gl和它的 React 相关包。package.json里声明:

{ "name": "kepler-heat-demo", "version": "1.0.0", "scripts": { "start": "vite" }, "dependencies": { "kepler.gl": "^3.0.0", "react": "^18.0.0", "react-dom": "^18.0.0", "styled-components": "^5.3.0" }, "devDependencies": { "vite": "^4.0.0" } }

安装用npm install。这里用 Vite 起本地服务,是因为它默认支持静态资源直接引用,points.csv放在根目录就能通过相对路径读到,不需要额外配 loader。如果你用 webpack,需要确认 csv 被当作静态资源处理,否则会报模块解析错误。

提示:kepler.gl对styled-components有版本要求,装错大版本会出现样式丢失或组件报错,遇到白屏先检查这个依赖。

3.2 加载 CSV 并初始化 KeplerGl 组件

main.js的核心逻辑是:读 CSV、转成 Kepler.gl 需要的行数据格式、初始化组件、在onCreate回调里添加热力图图层。

import React, { useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import { KeplerGl } from "kepler.gl"; import { addDataToMap } from "kepler.gl/actions"; import { Provider, useDispatch } from "react-redux"; import keplerGlReducer from "kepler.gl/reducers"; import { createStore, combineReducers } from "redux"; // 极简 store,只挂 keplerGl 的 reducer const store = createStore(combineReducers({ keplerGl: keplerGlReducer })); function parseCsv(text) { const lines = text.trim().split("\n"); const header = lines[0].split(","); return lines.slice(1).map((line) => { const cells = line.split(","); const row = {}; header.forEach((h, i) => { // 经纬度和权重转数值,其余保留字符串 row[h] = ["latitude", "longitude", "weight"].includes(h) ? parseFloat(cells[i]) : cells[i]; }); return row; }); } function App() { const dispatch = useDispatch(); const [rows, setRows] = useState([]); useEffect(() => { fetch("/points.csv") .then((r) => r.text()) .then((text) => setRows(parseCsv(text))); }, []); useEffect(() => { if (!rows.length) return; dispatch( addDataToMap({ datasets: { info: { label: "points", id: "points" }, data: { fields: [ { name: "latitude", type: "real" }, { name: "longitude", type: "real" }, { name: "weight", type: "integer" }, { name: "event_type", type: "string" }, ], rows, }, }, option: { centerMap: true, readOnly: false }, config: { version: "v1", config: { visState: { layers: [ { id: "heat-1", type: "heatmap", config: { dataId: "points", label: "heat", columns: { lat: "latitude", lng: "longitude" }, isVisible: true, visConfig: { opacity: 0.8, radius: 30, weight: { field: "weight" }, colorRange: { name: "Global Warming", colors: ["#5A1846", "#900C3F", "#C70039", "#E3611C", "#F1920E", "#FFC300"], }, }, }, visualChannels: { weightField: { name: "weight", type: "integer" }, colorField: { name: "weight", type: "integer" }, }, }, ], }, }, }, }) ); }, [rows, dispatch]); return ( <div style={{ position: "absolute", width: "100%", height: "100%" }}> <KeplerGl id="map" width={window.innerWidth} height={window.innerHeight} /> </div> ); } createRoot(document.getElementById("root")).render( <Provider store={store}> <App /> </Provider> );

这段代码有几个关键点需要说明。fields数组里显式声明了每个字段的类型,real对应浮点经纬度,integer对应权重,string对应分类字段。如果不声明,Kepler.gl 会自己猜,猜错时热力图可能不渲染。config.visState.layers里直接写死了图层配置,这样页面一加载就是热力图,不需要用户手动点。weightField和colorField都指向weight,意味着颜色深浅和热力强度都按权重走。

index.html只需要一个挂载点和入口脚本:

<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>kepler heat demo</title> </head> <body> <div id="root"></div> <script type="module" src="/main.js"></script> </body> </html>

启动用npm run start,浏览器打开本地地址,应该能看到三个颜色深浅不同的热力团。如果只看到一片均匀颜色,先检查weight字段是否被正确识别为数值。

3.3 热力图三个必调参数:radius、opacity、colorRange

热力图好不好看,八成取决于这三个参数。

radius控制每个点的影响半径,单位是像素。值太小,点与点之间不融合,看起来像散点图;值太大,整个图糊成一团,聚集边界消失。我一般从 20 开始试,数据点密集就调到 30 到 40,稀疏就降到 10 到 15。这个参数没有绝对标准,取决于地图缩放级别和点密度。

opacity是图层透明度,范围 0 到 1。热力图叠在底图上,透明度太低会盖住路网和地名,太高又看不清热力差异。0.7 到 0.85 是比较稳的区间。

colorRange是颜色带。Kepler.gl 内置了若干配色方案,热力图常用的是从深紫到亮黄这类渐变,深色代表低密度,亮色代表高密度。如果你有品牌色要求,可以自定义colors数组,但要注意颜色数量会影响分级平滑度,一般给 6 个左右比较自然。

这三个参数在代码里对应visConfig下的radius、opacity、colorRange。改完重新加载页面即可看到效果,不需要重启服务。

4. 避坑与常见问题排查

4.1 地图一片空白,控制台没有报错

现象是页面框架出来了,但地图区域全白,控制台干净。原因通常是容器高度为 0。Kepler.gl 的根组件需要明确的宽高,如果父容器没有撑开,地图就没有渲染空间。解决办法是给外层div设position: absolute; width: 100%; height: 100%,并确认body和html也有高度。另一个可能是KeplerGl组件的width/height传了 0,检查window.innerWidth是否在渲染时可用。

4.2 热力图不显示,只看到底图

现象是数据加载了,但图层列表里没有热力图,或者有图层但图上没颜色。原因多半是字段类型没声明对。Kepler.gl 对经纬度字段类型敏感,如果latitude被识别成字符串,热力图图层会静默失败。解决方式是在addDataToMap的fields里显式写type: "real",并确认 CSV 里没有混入非数值字符。还有一个隐蔽情况是经纬度写反了,纬度超过 90 或经度超过 180,点会被丢到地图外。

4.3 权重字段不生效,颜色深浅一样

现象是明明有weight列,但热力图颜色均匀。原因是visualChannels里没有绑定weightField,或者绑定的字段类型不是数值。Kepler.gl 的权重只认数值字段,字符串字段会被忽略。解决方式是在visualChannels.weightField里写{ name: "weight", type: "integer" },并确认fields里该字段类型一致。如果权重列里有空值,这些行会按 0 处理,也会拉平颜色差异,清洗时一并过滤。

4.4 数据量大时页面卡顿

现象是几万个点加载后拖动地图明显掉帧。原因是热力图默认在客户端逐点计算,点数过万后计算量上升。解决方式有两个方向:一是先在数据侧做聚合,把原始点按网格汇总成带权重的少量点,再喂给热力图;二是降低radius和关闭不必要的图层交互。我一般会在数据超过五万行时先做网格聚合,把点数量压到几千,视觉上几乎看不出差别,但流畅度提升明显。

4.5 导出的配置换台机器打不开

现象是把配置 JSON 导出后,在另一台机器导入,图层丢失或报字段找不到。原因是配置里引用了dataId,而新环境没有同名数据集。Kepler.gl 的配置和数据是分离的,导入配置前必须先加载同名的数据。解决方式是在代码里把datasets.info.id固定成一个常量,配置里的dataId引用同一个值,这样换环境只要数据在,配置就能复用。

5. 进阶:把热力图配置固化成可复用模板

做到这里,一张热力图已经能跑了。但实际项目里往往不是看一次,而是同一套配置反复看不同批次的数据。这时候把配置从代码里抽出来,做成一个 JSON 模板,会省很多事。

我的做法是先在在线版里把图层调满意,然后从 Kepler.gl 界面导出当前配置,得到一个 JSON 文件。这个文件里包含了visState、mapState、uiState等完整状态。把visState.layers这一段单独抽出来,存成heat-template.json,代码里读这个文件,再和数据集拼装后传给addDataToMap。这样调整样式不需要改代码,改 JSON 就行。

验证模板是否可复用,有一个简单方法:换一份字段名相同但数值范围不同的数据,看热力图是否自动适配。如果颜色全部挤在一端,说明colorRange的分级是固定的,需要根据数据分布动态调整。Kepler.gl 支持在visConfig里用colorRange配合colorScale做相对分级,但更稳的做法是提前算好数据的分位数,把分级边界写进配置。

另一个进阶点是热力图和点图层的叠加。热力图看整体密度,点图层看具体位置,两者叠在一起,既能看聚集趋势,又能定位到单个事件。做法是在layers数组里加一个type: "point"的图层,dataId指向同一份数据,把radius调小、opacity调低,避免盖住热力颜色。这种组合在排查「某个异常点是否落在高密度区」时特别有用。

最后说一个我自己的习惯:每次调完参数,先把配置 JSON 存一份带日期的副本,再改代码。热力图参数是玄学,同一个radius在不同数据上效果差很多,没有存档的话,改崩了想回到上一版只能凭记忆重调。这个习惯帮我省过好几次返工。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询