城脉 CityPulse — AI时空热力可视化平台

0 阅读8分钟

城脉 CityPulse — AI时空热力可视化平台

参赛作品 | 腾讯位置服务 × CSDN 征文大赛 主题:AI赋能 重塑地图智能新体验 技术栈:HTML5 + CSS3 + JavaScript + 腾讯位置服务 JS API GL + ECharts


一、项目概述

在城市化进程不断加速的今天,城市人流动向的实时感知已成为智慧城市建设的核心命题。传统的静态数据看板只能展示某一时刻的快照,无法揭示城市脉搏的动态演变规律。城脉 CityPulse 由此而生——一款基于腾讯位置服务 JS API GL 的 AI 时空热力可视化平台,将城市级人流、车流、消费热力数据以3D热力图的形式实时渲染,让用户一眼看清城市脉动。

核心能力:

  • 🏙️ 8大城市实时切换:北京、上海、广州、深圳、成都、杭州、武汉、西安
  • ⏱️ 24帧时空动画:还原城市热力从凌晨到深夜的演变规律
  • 📊 ECharts 统计分析:密度折线图 + 雷达图,双视角洞察城市活力
  • 🧠 AI 洞察引擎:基于热力数据自动生成城市活力分析报告
  • 📤 多格式数据导出:CSV / JSON / GeoJSON

请在此添加图片描述


二、设计理念

2.1 为什么选择「热力可视化」?

城市热力图是最直观、最高密度的空间数据表达形式。相比于散点图,热力图通过颜色渐变(深蓝→橙→红)将密度信息压缩到一个视觉通道,让用户无需阅读任何数字就能感知「哪里热闹、哪里冷清」。而加入时间维度后,热力图从静态快照升级为时空纪录片,这才是真正有价值的城市数据叙事。

请在此添加图片描述

2.2 视觉语言:深色科技风(Dark Science Fiction)

设计参考深色科技风格,以深蓝黑为底,辅以科技蓝(#00d4ff)和紫罗兰(#7c3aed)作为点缀。高对比度确保数据在高密度热力叠加时依然清晰可辨。整体视觉调性与腾讯位置服务本身的科技感高度吻合。

元素色值用途
科技蓝#00d4ff主强调色,热力峰值
紫罗兰#7c3aed次级强调
警示橙#f97316中等密度热力
深蓝#1e3a8a低密度热力
背景#0a0e1a全局背景
面板#111827侧边栏/卡片背景

2.3 从「看数据」到「读懂城市」

平台不只是展示数据,更通过以下设计帮助用户解读数据:

  • 热点排行榜:实时排序 Top 10 热点区域,一眼看出生理点和冷区
  • 统计概览卡片:POI 总数、峰值区域、平均密度、热点数量四个关键指标秒级刷新
  • 时间轴:24帧时间轴 + 播放控制,拖动进度条可精确定位到某一时刻

三、技术架构

3.1 整体架构

 ┌─────────────────────────────────────────────────┐
 │                    HTML5 页面结构                   │
 ├──────────┬─────────────────────────┬────────────┤
 │ 控制面板  │       腾讯地图容器 (GL)       │  信息面板  │
 │ (左侧)   │   #tencentMap (3D热力图)   │  (右侧)   │
 ├──────────┴─────────────────────────┴────────────┤
 │              CSS3 样式层 (深色科技风)                │
 ├─────────────────────────────────────────────────┤
 │  app.js (主逻辑) │ qqmap-adapter.js (地图适配)     │
 │  ┌─────────────┐ │ ┌─────────────────────────────┐│
 │  │  数据生成器  │ │ │ 腾讯地图 JS API GL 封装       ││
 │  │  事件绑定   │ │ │ init / addHeatmap / addMarkers││
 │  │  ECharts   │ │ │ switchCity / toggle3D / reset ││
 │  │  AI洞察    │ │ └─────────────────────────────┘│
 │  └─────────────┘                                  │
 └─────────────────────────────────────────────────┘

3.2 模块设计

地图适配层:qqmap-adapter.js

适配器模式将腾讯地图 API 封装为统一的内部接口,屏蔽底层差异:

// 初始化地图并设置城市中心
adapter.init('tencentMap').then(() => {
    adapter.addHeatmap(heatData, { radius: 80, intensity: 0.5 });
    adapter.addMarkers(hotspots.map(h => ({ lat: h.lat, lng: h.lng, name: h.name })));
});

// 切换城市
adapter.switchCity('上海');

// 切换 3D/2D 视图
adapter.toggle3D(); // 返回新的 is3D 状态

// 重置视角
adapter.resetView();

// 设置俯角
adapter.setPitch(45);
数据生成层:app.jslMD()

城市数据(8城 × 24帧 × ~120点/帧)通过数学模型实时生成,模拟真实城市热力分布规律:

function lMD() {
    var cl = ["北京", "上海", "广州", ...];
    var cm = { "北京": [39.9042, 116.4074], ... };
    var hm = { "北京": ["国贸CBD", "中关村", ...], ... };

    cl.forEach(function(city) {
        var cx = cm[city];
        var frames = [];
        // 每城市生成 24 帧(每帧代表一天中的一小时)
        for (var f = 0; f < 24; f++) {
            var points = [];
            var count = 80 + Math.floor(Math.random() * 40);
            for (var i = 0; i < count; i++) {
                // 在城市中心点 0.15° 范围内随机撒点
                var lat = cx[0] + (Math.random() - 0.5) * 0.15;
                var lng = cx[1] + (Math.random() - 0.5) * 0.15;
                // 模拟正弦波动的热力权重(高峰时段权重更高)
                var hourFactor = Math.sin((f / 24) * Math.PI * 2 - Math.PI / 2) * 0.5 + 0.5;
                var weight = 0.3 + Math.random() * 0.7 * hourFactor;
                points.push({ lat, lng, weight });
            }
            frames.push(points);
        }

        // 生成热点标记(围绕城市中心环形分布)
        var hotspots = hm[city].map((name, i) => {
            var angle = (i / hm[city].length) * Math.PI * 2;
            var dist = 0.02 + Math.random() * 0.05;
            return {
                name,
                lat: cx[0] + Math.sin(angle) * dist,
                lng: cx[1] + Math.cos(angle) * dist,
                value: Math.floor(600 + Math.random() * 800),
                category: ['商业', '交通', '居住', '娱乐', '办公'][i % 5]
            };
        });

        S.cD[city] = { center: { lat: cx[0], lng: cx[1] }, frames, hotspots };
    });
}
动画引擎:app.jspAnim()
function pAnim() {
    if (!S.play) return;
    S.cf = (S.cf + 1) % 24; // 循环播放 24 帧
    var cityData = S.cD[S.cC];
    if (cityData) {
        S.hD = cityData.frames[S.cf] || [];
        rHM(); // 刷新热力图层
        upTLUI(); // 更新时间轴 UI
    }
    var interval = Math.round(500 / S.spd); // 支持 1x~10x 变速
    S.pt = setTimeout(pAnim, interval);
}

四、核心功能演示

4.1 时空动画播放

点击「播放时空动画」按钮,系统自动以可配置的速率(1x~10x)循环播放24帧热力演变。进度条实时反映当前帧位置,点击进度条任意位置可跳转。

4.2 AI 洞察分析

点击顶部「🧠 AI洞察分析」按钮,平台在1.5秒内分析当前城市、当前帧的全部热力数据,生成包含以下内容的结构化报告:

  • 核心发现:识别最热区域(峰值热点)、热力走廊分布形态、次级热点增长率
  • 时段特征:结合当前帧对应时段(凌晨/早高峰/午间/晚高峰等)解读人流规律
  • 区域活力评分:综合人口密度、出行轨迹、时段特征给出 60-90 分的量化评分
  • 出行建议:基于热力趋势预测未来2小时状态,提供错峰出行建议

4.3 数据导出

支持 CSV(带 BOM 的 UTF-8 编码,Excel 完美兼容)、JSON、GeoJSON 三种格式。导出范围可选当前帧或全部24帧时间轴数据,可直接用于后续数据分析或二次可视化。

4.4 3D 视角与视觉调参

  • 3D/2D 切换:一键切换立体视角,增强空间纵深感
  • 俯视角控制:0°~60° 俯角滑块控制,观察热力垂直分布
  • 热力强度 / 半径 / 高度倍数:三个视觉参数实时调整,即时反映在地图上

请在此添加图片描述


五、关键代码解读

5.1 腾讯地图 GL 热力图层封装

class QQMapAdapter {
  constructor() {
    this.map = null;
    this.heatmapOverlay = null;
    this.markers = [];
    this.is3D = false;
  }

  async init(containerId) {
    return new Promise((resolve) => {
      this.map = new TMap.Map(containerId, {
        center: new TMap.LatLng(39.9042, 116.4074),
        zoom: 12,
        pitch: 45,
        mapStyleId: 'style_night', // 深色地图底图
      });
      this.map.on('init', () => resolve());
    });
  }

  addHeatmap(points, opts = {}) {
    const radius = opts.radius || 80;
    const intensity = opts.intensity || 0.5;
    const data = points.map(p => ({
      ...new TMap.LatLng(p.lat, p.lng),
      weight: p.weight || 1
    }));

    if (this.heatmapOverlay) {
      this.map.removeLayer(this.heatmapOverlay);
    }

    this.heatmapOverlay = new TMap.visualization.Heatmap({
      radius: radius,
      intensity: intensity,
      opacity: opts.opacity || 0.85,
      gradient: { // 自定义蓝→橙→红渐变
        0.0: '#1e3a8a',
        0.4: '#f97316',
        0.8: '#ef4444',
        1.0: '#dc2626'
      }
    });

    this.map.addLayer(this.heatmapOverlay);
    this.heatmapOverlay.setData(data);
  }

  addMarkers(hotspots) {
    this.markers.forEach(m => this.map.remove(m));
    this.markers = hotspots.map(h => {
      const marker = new TMap.MultiMarker({
        id: 'hotspot-markers',
        map: this.map,
        styles: {
          marker: new TMap.MarkerStyle({
            width: 24,
            height: 35,
            anchor: { x: 12, y: 35 },
            color: '#00d4ff',
            size: 16
          })
        },
        geometries: [{
          id: h.name,
          styleId: 'marker',
          position: new TMap.LatLng(h.lat, h.lng)
        }]
      });
      return marker;
    });
  }

  switchCity(city) {
    const centers = {
      "北京": [39.9042, 116.4074],
      "上海": [31.2304, 121.4737],
      // ...
    };
    const [lat, lng] = centers[city];
    this.map.setCenter(new TMap.LatLng(lat, lng));
  }

  toggle3D() {
    this.is3D = !this.is3D;
    this.map.setPitch(this.is3D ? 45 : 0);
    return this.is3D;
  }

  resetView() {
    this.map.resetViewport();
  }

  setPitch(degrees) {
    this.map.setPitch(degrees);
  }

  flashMarker(name, lat, lng) {
    // 高亮闪烁指定标记
  }
}

5.2 ECharts 密度折线图

S.chs.d = echarts.init(D.densityChart);
S.chs.d.setOption({
  backgroundColor: 'transparent',
  grid: { top: 30, right: 15, bottom: 25, left: 40 },
  xAxis: {
    type: 'category',
    data: ['06', '08', '10', '12', '14', '16', '18', '20', '22'],
    axisLine: { lineStyle: { color: '#334155' } },
    axisLabel: { color: '#64748b', fontSize: 10 }
  },
  yAxis: {
    type: 'value',
    splitLine: { lineStyle: { color: '#1e293b' } },
    axisLabel: { color: '#64748b', fontSize: 10 }
  },
  series: [{
    type: 'line',
    smooth: true,
    data: [320, 450, 680, 920, 780, 1050, 1200, 880, 520],
    areaStyle: {
      color: {
        type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
        colorStops: [
          { offset: 0, color: 'rgba(0,212,255,0.4)' },
          { offset: 1, color: 'rgba(0,212,255,0.05)' }
        ]
      }
    },
    lineStyle: { color: '#00d4ff', width: 2 },
    itemStyle: { color: '#00d4ff' },
    symbol: 'circle', symbolSize: 4
  }],
  tooltip: {
    trigger: 'axis',
    backgroundColor: '#1e293b',
    borderColor: '#334155',
    textStyle: { color: '#f1f5f9', fontSize: 12 }
  }
});

六、作品亮点

6.1 为什么这个作品有竞争力?

① 时空维度是差异点。大多数参赛作品展示的是静态热力,而 CityPulse 通过24帧动画揭示城市热力的一天变化规律——这是腾讯位置服务热力图层能力的完整展现。

② 技术栈纯净,依赖腾讯官方。完全基于腾讯位置服务 JS API GL + ECharts 构建,未引入第三方可视化库(与活动「使用腾讯位置服务」的主题高度契合)。

③ 从数据到叙事。AI 洞察模块将纯数字热力数据转化为自然语言描述,让非技术用户也能「读懂」城市。

④ 全功能闭环。数据生成 → 可视化 → 分析 → 导出,形成了完整的数据消费链路。

6.2 技术局限与未来方向

  • 当前数据为数学模型模拟,尚未接入腾讯位置服务的真实热力数据 API(个人认证不支持)
  • 3D 高度倍数目前仅调整热力半径,未来可结合 Extrude 实现真正的3D柱状热力
  • AI 洞察为规则引擎,可升级为接入大模型 API 的真正 AI 分析

七、结语

城脉 CityPulse 将腾讯位置服务 GL 版的 3D 地图能力、热力图层与时空动画结合,探索了一种「用眼睛看城市脉搏」的交互范式。希望通过这个作品,让更多人感受到城市数据的生命力和空间智能的魅力。