【2025最新】ArcGIS for JS 实现随着时间变化而变化的热力图

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. 核心逻辑:图层配置与时间轴联动)
    • [三、所有代码 📊](#三、所有代码 📊)
    • [四、常见问题排查 ❌](#四、常见问题排查 ❌)
工具 /插件/系统 名 版本 说明
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>

四、常见问题排查 ❌

  1. 热力图不显示?
  • 检查 CSV 文件路径是否正确(相对路径需与 HTML 同级);

  • 确认 CSV 字段名与代码一致(如 "销售量" 不能错写为 "销量");

  • 打开浏览器控制台(F12),查看是否有数据加载错误。

  1. 时间轴拖动无反应?
  • 检查timeInfostartFieldendField是否为 CSV 中的时间字段;

  • 确认timeSliderfullTimeExtent与数据的时间范围匹配。

  1. 弹窗不显示?
  • 检查popupTemplate中的字段名是否与 CSV 一致(如{地区}需对应 CSV 的 "地区" 字段)。

通过以上步骤,就能快速实现一个 "随时间动态变化的热力图" 啦!无论是商品房销售、人口流动还是交通流量分析,这个方案都能直接复用,只需替换 CSV 数据和调整渲染参数即可。如果有更复杂的需求(如多维度筛选、自定义底图),可以进一步扩展 ArcGIS API 的其他模块~

#ArcGISforJS

相关推荐
Pluto_CSND4 小时前
Java实现gRPC双向流通信
java·开发语言·单元测试
拉不动的猪5 小时前
h5后台切换检测利用visibilitychange的缺点分析
前端·javascript·面试
原来是猿5 小时前
谈谈环境变量
java·开发语言
应用市场5 小时前
本地局域网邮件管理系统:从原理到实现的完整指南
开发语言
桃子不吃李子5 小时前
nextTick的使用
前端·javascript·vue.js
Tony Bai5 小时前
【Go 网络编程全解】12 本地高速公路:Unix 域套接字与网络设备信息
开发语言·网络·后端·golang·unix
oioihoii5 小时前
深入理解 C++ 现代类型推导:从 auto 到 decltype 与完美转发
java·开发语言·c++
报错小能手5 小时前
项目——基于C/S架构的预约系统平台 (1)
开发语言·c++·笔记·学习·架构