ArcGIS for JavaScript 实现随着时间变化而变化的热力图
在数据可视化领域,热力图是展现空间数据密度分布的 "利器",而随时间动态变化的热力图 更能直观呈现数据的时空演变规律 ------ 比如城市商品房月度销售热度的变化趋势。本文适用于 ArcGIS Maps SDK for JavaScri pt 4.28~4.33 版本,实现热力图随月份动态切换的效果,本文为热力图渲染进阶版,若想查看基础可查阅【2025最新】ArcGIS for JavaScript 快速实现热力图渲染 🗺️📊
文章目录
- [ArcGIS for JavaScript 实现随着时间变化而变化的热力图](#ArcGIS for JavaScript 实现随着时间变化而变化的热力图)
-
- [一、前置准备:环境与数据 📦](#一、前置准备:环境与数据 📦)
-
- [1. 核心技术栈](#1. 核心技术栈)
- [2. 数据格式示例(商品房.csv)](#2. 数据格式示例(商品房.csv))
- [二、核心代码实现:从 0 到 1 搭建动态热力图 🔧](#二、核心代码实现:从 0 到 1 搭建动态热力图 🔧)
-
- [1. 页面结构与样式基础](#1. 页面结构与样式基础)
- [2. 核心逻辑:图层配置与时间轴联动](#2. 核心逻辑:图层配置与时间轴联动)
-
- (1)模块导入与底图加载
- [(2)CSV 图层配置:定义热力图渲染规则](#(2)CSV 图层配置:定义热力图渲染规则)
- (3)地图与时间轴初始化
- [(4)关键联动:时间轴变化 → 热力图更新](#(4)关键联动:时间轴变化 → 热力图更新)
- [三、所有代码 📊](#三、所有代码 📊)
- [四、常见问题排查 ❌](#四、常见问题排查 ❌)
| 工具 /插件/系统 名 | 版本 | 说明 |
|---|---|---|
| ArcGIS JS API | 4.28~4.33 | 地图核心能力(底图加载、视图渲染) |
| 天地图服务 | - | 提供街道、卫星、地形等底图数据源 |

一、前置准备:环境与数据 📦
1. 核心技术栈
-
ArcGIS API for JavaScript 4.33:提供地图渲染、图层管理、时间轴控件等核心能力
-
天地图底图:通过自定义加载器接入,让地图更贴合国内场景
-
CSV 数据:存储商品房销售数据,需包含关键字段:
-
地理坐标:
经度、纬度(用于定位空间位置) -
时间维度:
月份(格式如 2024-01,用于时间轴筛选) -
数值维度:
销售量(热力图的 "密度来源")
-
2. 数据格式示例(商品房.csv)
| 地区 | longitude | latitude | 月份 | 销售量 |
|---|---|---|---|---|
| 朝阳区 | 116.48 | 39.93 | 2024-01 | 2850 |
| 海淀区 | 116.31 | 39.99 | 2024-01 | 3210 |
| 朝阳区 | 116.48 | 39.93 | 2024-02 | 2560 |
二、核心代码实现:从 0 到 1 搭建动态热力图 🔧
1. 页面结构与样式基础
首先创建 HTML 骨架,定义地图容器和时间轴容器,并设置全屏样式(确保地图占满页面):
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>商品房销售时间轴热力图</title>
<!-- 引入ArcGIS核心资源 -->
<script type="module" src="https://js.arcgis.com/4.33/map-components/"></script>
<link rel="stylesheet" href="https://js.arcgis.com/4.33/esri/themes/light/main.css" />
<style>
/* 地图全屏样式 */
html, body, #viewDiv {
padding: 0; margin: 0;
height: 100%; width: 100%;
}
/* 时间轴固定在底部 */
#time-slider {
position: absolute;
left: 5%; right: 5%; bottom: 20px;
z-index: 100; /* 确保在地图上方显示 */
}
</style>
</head>
<body>
<div id="viewDiv"></div> <!-- 地图容器 -->
<div id="time-slider"></div> <!-- 时间轴容器 -->
</body>
</html>
2. 核心逻辑:图层配置与时间轴联动
这部分是 "动态热力图" 的关键,主要实现 3 个核心功能:
① 加载 CSV 数据并配置热力图渲染;② 初始化时间轴控件;③ 监听时间轴变化,实时更新热力图。
(1)模块导入与底图加载
先导入 ArcGIS 所需模块,再加载天地图底图(需申请天地图 key,也可以直接在《天地图底图加载》)中直接复制,如果没有 key,可先用 ArcGIS 自带底图(如basemap: "streets")临时测试。:
<script type="module">
// 导入ArcGIS核心模块
const [Map, CSVLayer, MapView, Legend, TimeSlider, reactiveUtils] = await $arcgis.import([
"@arcgis/core/Map.js",
"@arcgis/core/layers/CSVLayer.js",
"@arcgis/core/views/MapView.js",
"@arcgis/core/widgets/Legend.js",
"@arcgis/core/widgets/TimeSlider.js",
"@arcgis/core/core/reactiveUtils.js",
]);
// 加载天地图底图(自定义加载器)
import { loadTiandituBasemap } from './js/tiandituLoader.js';
const { tiandituBasemap } = await loadTiandituBasemap();
</script>
(2)CSV 图层配置:定义热力图渲染规则
通过CSVLayer加载销售数据,并配置热力图渲染器 (颜色渐变、密度字段等)和时间信息(时间字段、时间范围):
// 1. 数据弹窗模板(点击热力图时显示的信息)
const template = {
title: "{地区}",
content: "{月份} - 销售量:{销售量}套",
};
// 2. 热力图渲染器(核心!控制颜色渐变和密度计算)
const renderer = {
type: "heatmap",
colorStops: [ // 颜色从透明黄到红棕色渐变,数值越高颜色越深
{ color: "rgba(255, 255, 0, 0)", ratio: 0 }, // 透明(无数据)
{ color: "#ffff00", ratio: 0.083 }, // 亮黄
{ color: "#ff9800", ratio: 0.332 }, // 橙色
{ color: "#ff4500", ratio: 0.581 }, // 橙红
{ color: "#b30f00", ratio: 1 } // 红棕(最高密度)
],
field: "销售量", // 用于计算密度的字段(销售量越高,热力越集中)
maxDensity: 58, // 初始最大密度(后续会动态更新)
minDensity: 0,
};
// 3. 时间信息配置(定义时间维度规则)
const timeInfo = {
startField: "月份", // 开始时间字段
endField: "月份", // 结束时间字段(因是月度数据,起止一致)
fullTimeExtent: { // 完整时间范围(2024年1-12月)
start: new Date("2024-01"),
end: new Date("2024-12")
},
timeInterval: { // 时间间隔(1个月)
value: 1,
unit: "months"
}
};
// 4. 创建CSV图层
const layer = new CSVLayer({
url: "./商品房.csv", // 数据文件路径
title: "商品房月度销售热力",
popupTemplate: template, // 弹窗模板
renderer: renderer, // 热力图渲染器
timeInfo: timeInfo, // 时间信息
timeEnabled: true, // 启用时间筛选
outFields: ["*"], // 返回所有字段
});
(3)地图与时间轴初始化
创建地图视图,并初始化时间轴控件(TimeSlider),设置时间范围为 2024 年全年,间隔 1 个月:
// 1. 创建地图(绑定底图和CSV图层)
const map = new Map({
basemap: tiandituBasemap,
layers: [layer],
});
// 2. 创建地图视图(定位到北京,缩放级别10)
const view = new MapView({
container: "viewDiv",
center: [116.4074, 39.9042], // 北京经纬度
zoom: 10,
map: map,
});
// 3. 添加图例控件(右上角显示)
view.ui.add(
new Legend({ view: view }),
"top-right"
);
// 4. 创建时间轴控件
const timeSlider = new TimeSlider({
container: "time-slider", // 绑定容器
view: view,
mode: "instant", // 即时更新模式(拖动滑块立即刷新)
fullTimeExtent: {
start: new Date("2024-01-01"),
end: new Date("2024-12-01"),
},
stops: { // 时间轴刻度(每月1个刻度)
interval: { value: 1, unit: "months" }
},
labelsVisible: true, // 显示时间标签
});
(4)关键联动:时间轴变化 → 热力图更新
通过reactiveUtils.watch监听时间轴的timeExtent变化,实时筛选当前月份的数据,并动态调整热力图最大密度(确保不同月份的热力对比更准确):
// 监听时间轴变化
reactiveUtils.watch(
() => timeSlider.timeExtent, // 监听时间范围变化
(timeExtent) => {
// 1. 格式化当前选中的时间(提取日期部分,如2024-01-01)
const start = timeExtent.start.toISOString().split('T')[0];
const where = `月份 = DATE '${start}'`; // SQL筛选条件(当前月份)
// 2. 筛选当前月份的数据(只显示选中月份的热力)
const layerView = await view.whenLayerView(layer);
layerView.featureEffect = { filter: { where } };
// 3. 动态更新热力图最大密度(根据当前月份的最大销售量计算)
layer.queryFeatures({
returnGeometry: false, // 不需要几何信息,只查属性
outStatistics: [{ // 统计当前月份的最大销售量
onStatisticField: "销售量",
outStatisticFieldName: "max_sales",
statisticType: "max"
}],
where: where // 筛选当前月份
}).then(result => {
const maxSales = result.features[0].attributes['max_sales'];
layer.renderer.maxDensity = maxSales / 100; // 调整密度系数(根据数据量级优化)
console.log(`当前月份最大销售量:${maxSales}套,热力最大密度:${maxSales/100}`);
});
}
);
// 图层加载完成后,自动定位到数据范围
layer.when(() => {
console.log("CSV图层加载完成!");
view.goTo(layer.fullExtent.expand(1.5)); // 扩大1.5倍范围,避免数据贴边
}).catch((error) => {
console.error("图层加载失败:", error);
});
三、所有代码 📊
index.html
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>热力图</title>
<script type="module" src="https://js.arcgis.com/4.33/map-components/"></script>
<link rel="stylesheet" href="https://js.arcgis.com/4.33/esri/themes/light/main.css" />
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#time-slider {
position: absolute;
left: 5%;
right: 5%;
bottom: 20px;
}
</style>
<script src="https://js.arcgis.com/4.33/"></script>
<script type="module">
const [Map, CSVLayer, MapView, Legend, TimeSlider, reactiveUtils] = await $arcgis.import([
"@arcgis/core/Map.js",
"@arcgis/core/layers/CSVLayer.js",
"@arcgis/core/views/MapView.js",
"@arcgis/core/widgets/Legend.js",
"@arcgis/core/widgets/TimeSlider.js",
"@arcgis/core/core/reactiveUtils.js",
]);
// 天地图加载模块
import { loadTiandituBasemap } from './js/tiandituLoader.js';
const { tiandituBasemap } = await loadTiandituBasemap();
const url = "./商品房.csv";
const template = {
title: "{地区}",
content: "{月份} - 销售量 {销售量}.",
};
// 最大像素强度用于指定颜色
// 从colorStops属性中的连续颜色渐变
const renderer = {
type: "heatmap",
colorStops: [
{ color: "rgba(255, 255, 0, 0)", ratio: 0 }, // 透明黄色(起始)
{ color: "#ffff00", ratio: 0.083 }, // 亮黄色
{ color: "#ffd700", ratio: 0.166 }, // 金黄色
{ color: "#ffc107", ratio: 0.249 }, // 琥珀色
{ color: "#ff9800", ratio: 0.332 }, // 橙色
{ color: "#ff7043", ratio: 0.415 }, // 深橙色
{ color: "#ff5722", ratio: 0.498 }, // 橙红色
{ color: "#ff4500", ratio: 0.581 }, // 橙红色(加深)
{ color: "#ff3a00", ratio: 0.664 }, // 浅红色
{ color: "#ff2400", ratio: 0.747 }, // 红色(加深)
{ color: "#e61900", ratio: 0.83 }, // 深红色
{ color: "#cc1400", ratio: 0.913 }, // 暗深红色
{ color: "#b30f00", ratio: 1 } // 红棕色(终点)
],
field: "销售量",
maxDensity: 58,
minDensity: 0,
};
// 时间字段配置
const timeInfo = {
startField: "月份",
endField: "月份",
fullTimeExtent: {
start: new Date("2024-01"),
end: new Date("2024-12")
},
timeInterval: {
value: 1,
unit: "months"
}
};
const layer = new CSVLayer({
url: url,
title: "热力显示",
copyright: "模拟数据",
popupTemplate: template,
renderer: renderer,
timeInfo: timeInfo,
timeEnabled: true,
labelsVisible: true,
outFields: ["*"]
});
const map = new Map({
basemap: tiandituBasemap,
layers: [layer],
});
const view = new MapView({
container: "viewDiv",
center: [116.4074, 39.9042],
zoom: 10,
map: map,
});
view.ui.add(
new Legend({
view: view,
}),
"top-right",
);
const layerView = await view.whenLayerView(layer);
// 创建时间滑块
const timeSlider = new TimeSlider({
container: "time-slider",
view: view,
mode: "instant",
fullTimeExtent: {
start: new Date("2024-01-01"),
end: new Date("2024-12-01"),
},
stops: {
interval: {
value: 1,
unit: "months",
},
},
labelsVisible: true
});
// 确保图层加载完成后再显示
layer.when(() => {
console.log("图层加载完成");
view.goTo(layer.fullExtent.expand(1.5));
}).catch((error) => {
console.error("图层加载失败:", error);
});
console.log("热力图应用已初始化");
reactiveUtils.watch(
() => timeSlider.timeExtent,
(timeExtent) => {
const start = timeExtent.start.toISOString().split('T')[0];
const end = timeExtent.end.toISOString().split('T')[0];
const where = `月份 = DATE '${start}' `
layerView.featureEffect = {
filter: { where }
};
// 查询所有要素的销售量,取最大值
layer.queryFeatures({
returnGeometry: false,
outStatistics: [{
// 要计算最大值的字段(需与 CSV 中字段名完全一致,含中文和括号)
onStatisticField: "销售量",
// 自定义统计结果的字段名(后续从结果中通过此名获取最大值)
outStatisticFieldName: "max_sales",
// 统计类型:最大值
statisticType: "max"
}],
where
}).then(result => {
const maxSales = result.features[0].attributes['max_sales'];
// 动态更新渲染器的最大密度
layer.renderer.maxDensity = maxSales / 100;
console.log(`最大销售量:${maxSales / 100}`);
});
},
);
</script>
</head>
<body>
<div id="viewDiv">
</div>
<div id="time-slider"></div>
</body>
</html>
四、常见问题排查 ❌
- 热力图不显示?
-
检查 CSV 文件路径是否正确(相对路径需与 HTML 同级);
-
确认 CSV 字段名与代码一致(如 "销售量" 不能错写为 "销量");
-
打开浏览器控制台(F12),查看是否有数据加载错误。
- 时间轴拖动无反应?
-
检查
timeInfo的startField和endField是否为 CSV 中的时间字段; -
确认
timeSlider的fullTimeExtent与数据的时间范围匹配。
- 弹窗不显示?
- 检查
popupTemplate中的字段名是否与 CSV 一致(如{地区}需对应 CSV 的 "地区" 字段)。
通过以上步骤,就能快速实现一个 "随时间动态变化的热力图" 啦!无论是商品房销售、人口流动还是交通流量分析,这个方案都能直接复用,只需替换 CSV 数据和调整渲染参数即可。如果有更复杂的需求(如多维度筛选、自定义底图),可以进一步扩展 ArcGIS API 的其他模块~