基于百度地图JSAPI Three的城市公交客流可视化(一)——线路客流

基于百度地图JSAPI Three的城市公交客流可视化(一)------线路客流

前言

目前可视化大屏网站大致分为2/3D两个大类,据我所知3D大屏中的实现方式有客户端渲染和服务端渲染,客户端渲染主流的实现方式大概有threejsbabylonjs或者unity打包html的方式,而服务端则由UE像素流技术实现,当然他们也各有各的优缺点。

今天我们的需求是快速的实现在公交客流的炫酷的可视化展示(ps:一定要高级),我们需要在地图上展示最基础的GEO数据,像点、线等业务数据,目前市面上的有很多可用:

  • cesium:相对复杂

  • 高德地图:loca可视化拓展性低,叠加gl图层复杂

  • mapbox:(不会,嘻嘻)

调研了很多,百度地图居然出了基于threejs的版本,大致看了一下,不仅封装了很多模块例如:天气系统、动态天空(还包含大气散射、体积云)、众多投影方式和3D地球,这简直就是百度版cesium(bushi ,不仅能实现地图的基础业务还能拓展超级多的炫酷效果,废话不多说,让我们一块开始实现我们的业务。

需要实现的公交客流效果:线路客流 OD、区域客流 OD和实时公交 ,如图所示

技术准备和项目初始化

我们需要去百度地图开放平台申请AK(不是你想的那个),🔗:控制台 | 百度地图开放平台 这里跟着官网流程走就行

首先我们使用的技术栈是Vue3,安装我们需要使用的依赖:

  • @baidumap/mapv-three 百度JSAPI three

  • @turf/turf 空间计算工具

  • threejs

  • vite-plugin-static-copy (vite原生插件替代rollup-plugin-copy)

安装vite-plugin-static-copy后将以下主要代码写进vite.config.ts

javascript 复制代码
// vite-plugin-static-copy
import { viteStaticCopy } from 'vite-plugin-static-copy'

export default defineConfig({
  plugins: [
    viteStaticCopy({
      targets: [
        {
          src: 'node_modules/@baidumap/mapv-three/dist/assets/*',
          dest: 'mapvthree/assets',
        },
      ],
    }),

    // 其他插件...
  ],
  // ...其他配置
})

接着我们就去项目的index.html中加入MAPV_BASE_URL 全局变量指向静态资源目录,前提是一定要安装了mapv-three

html 复制代码
 <script>
      window.MAPV_BASE_URL = '/node_modules/@baidumap/mapv-three/dist' // 配置为mapv-three包路径的dist目录
  </script>

配置完成后初始化地图我们需要装填我们的AK,然后再开始真正的开发流程。

javascript 复制代码
<script setup lang="ts">
import { onMounted } from 'vue'
// 在项目入口文件中配置
import * as mapvthree from '@baidumap/mapv-three'
import type { Engine } from '@baidumap/mapv-three/dist/types/index.d.ts'

// 配置百度地图 AK
mapvthree.BaiduMapConfig.ak = 'xxxxxxxxxxxx'  //你自己申请的AK

onMounted(() => {
  const div = document.getElementById('engine') as HTMLDivElement | null
  if (!div) return

  const engine: Engine = new mapvthree.Engine(div, {
    map: {
      center: [116.404, 39.915],
      range: 7000,
      projection: 'EPSG:3857',
    },
    rendering: {
      enableAnimationLoop: true,
      sky: new mapvthree.DynamicSky({
        time: 3600 * 6,
      }), // 动态天空
    },
  } as any)
})
</script>
<template>
  <div id="engine" class="engine"></div>
</template>
<style scoped>
.engine {
  width: 100%;
  height: 100%;
}
</style>

技术实现

我们需要展示一条公交线路上不同站点乘客的流动情况,大白话就是乘客做几路公交车从哪里来,到哪里去,他们之间的关系用飞线在适合不过了,就像这样:

首先我们需要准备几个关键的数据文件:

  1. 站点数据 (stands.json) - 包含所有公交站点的位置和客流信息
  2. 飞线数据 (line.json) - 站点间的客流流向关系
  3. 线路数据 (route.json) - 公交线路的上下行路径

站点数据结构:

json 复制代码
{
  "stationId": 200,
  "stationName": "火车站",
  "stands": [
    {
      "id": 799,
      "name": "火车站",
      "lat": 33.3195783,
      "lon": 117.30184,
      "down": 219 // 下车人数
    }
  ]
}

飞线数据定义了客流流向:

json 复制代码
{
  "from": [{ "id": 765, "down": 2 }], // 从id:765站点出发
  "to": [{ "id": 561, "down": 2 }] // 到达id:561站点
}

站点可视化 - 热力柱

站点我们用热力柱来展示,高度代表客流强度。这里用到了mapv-three的Pillar热力柱:

ts 复制代码
// 展平所有站点数据
const allStands = stands.flatMap((s) => s.stands)
const features = allStands.map((s) => ({
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [wgs84tobd09(s.lon, s.lat)[0], wgs84tobd09(s.lon, s.lat)[1]],
    },
    properties: {
        id: s.id,
        name: s.name,
        down: s.down || 0, // 下车人数作为高度依据
    },
}))

// 创建热力柱
const pillar = new mapvthree.Pillar({
    shape: 'pillar',
    height: 500, // 基础高度
    radius: 10, // 柱子半径
    vertexHeights: true, // 启用顶点高度
    gradient: {
        0.02: '#467ce3', // 蓝色
        0.04: '#25ba3d', // 绿色
        0.06: '#b6cc4f', // 黄绿色
        0.5: '#b04932', // 红色
    },
})

// 设置高度映射
dataSource.defineAttribute('height', (p) => p.down * 2)

wgs84tobd09函数 是将硬件设备上常用的定位格式转为百度地图专用的格式

我们用了从蓝色到红色渐变色来区分不同客流强度,如图所示:

飞线效果实现

飞线是客流可视化的核心,但是百度地图文档中似乎并没有封装高自定义得飞线,但是好处是百度地图允许我们使用一切threejs中的功能,准备基于Three.js的Line功能,手写自定义的FlyLine类来实现这个类:

ts 复制代码
export class FlyLine extends Line {
  constructor(startBd09: LngLat, endBd09: LngLat, options?: FlyLineOptions) {
    // 坐标转换
    const start = lngLatToMercator(startBd09)
    const end = lngLatToMercator(endBd09)

    // 动态计算弧线高度
    const distance = start.distanceTo(end)
    const height = this.calculateHeight(distance, options)

    // 构建贝塞尔曲线
    const points = buildSymmetricArc(start, end, height, segments,offset)

    // 创建流光shader材质
    const shaderMaterial = new ShaderMaterial({
      uniforms: {
        uTime: { value: 0 },
        uSpeed: { value: speed },
        uBaseColor: { value: baseColor },
        uGlowColor: { value: glowColor },
      },
      vertexShader,
      fragmentShader,
      transparent: true,
      blending: AdditiveBlending, // 叠加混合产生发光效果
    })

    super(geometry, shaderMaterial)
  }
}

其中飞线主要核心函数是buildSymmetricArc ,主要原理是在两个平面坐标点之间,构建一条光滑的三维贝塞尔弧线,其顶点在连接线的上,从而形成一个拱形的多线段曲线,其中segments为线段平滑程度,offset为贝塞尔曲线偏移量 如图所示:

关键点在于让曲线动起来,这里采用流光shader的实现,我们在fragment来模拟光点沿着线条流动的效果,如果你是Vscode,推荐安装插件:WebGL GLSL Editor,这样可以抽离成glsl文件并且编写时有语法提示,这里我们暂用模板语法

c 复制代码
// 流光shader着色器
const fragmentShader = `
    uniform float uTime;
    uniform float uSpeed;
    uniform vec3 uBaseColor;
    uniform vec3 uGlowColor;

    varying float vU;

    void main() {
        // 计算流光位置
        float flowPos = fract(vU - uTime * uSpeed);

        // 主流光 - 明亮的光点
        float mainFlow = 1.0 - smoothstep(0.0, 0.1, abs(flowPos - 0.5));

        // 流光尾巴
        float trail = smoothstep(flowPos - uTrail, flowPos, vU);

        // 颜色混合,让流光部分超出RGB范围产生泛光
        vec3 color = mix(uBaseColor, uGlowColor, mainFlow + trail);
        color = mix(color, color * 3.0, mainFlow + trail);

        gl_FragColor = vec4(color, intensity * uOpacity);
    }
`;

飞线泛光效果

好了,为了让效果更炫酷,我们需要泛光效果,如果是在threejs中我们需要手动的增加后处理加入Bloom效果,分层、合成会较为繁琐,百度地图为我们考虑好了这点,我们直接在engine.rendering.features.bloom中即可开启场景泛光,只要我们的颜色超过RGB的范围,就会产生泛光

ts 复制代码
const engine = new mapvthree.Engine(div, {
    rendering: {
        features: {
            bloom: {
                enabled: true,
                strength: 10.5, // 泛光强度
                threshold: 0.1, // 泛光阈值
                radius: 1.4, // 泛光半径
            },
        },
    },
})

//物体泛红光:
const box = engine.add(new Mesh(new IcosahedronGeometry(5, 15), new MeshBasicMaterial({
    color: new Color(10, 0, 0), // 颜色超出RGB范围,会产生泛光效果
})));

这样飞线的流光效果就能产生真正的泛光了,看起来特别酷。

线路路径绘制

最后我们还需要绘制公交线路的上下行路径,这里直接使用自带的宽线就行,用不同颜色区分:

js 复制代码
// 上行线路 - 浅蓝色
const upLine = engine.add(
  new mapvthree.Polyline({
    flat: true,
    lineWidth: 4,
    color: '#87CEFA',
  }),
)

// 下行线路 - 红色
const downLine = engine.add(
  new mapvthree.Polyline({
    flat: true,
    lineWidth: 4,
    color: '#FF6B6B',
  }),
)

// 设置数据源
upLine.dataSource = mapvthree.GeoJSONDataSource.fromGeoJSON(upLineGeoJSON)
downLine.dataSource = mapvthree.GeoJSONDataSource.fromGeoJSON(downLineGeoJSON)

效果图

  1. 站点热力柱 - 用高度和渐变色展示客流强度
  2. 飞线效果 - 自定义shader实现流光动画,展示客流流向
  3. 线路路径 - 上下行用不同颜色区分
  4. 泛光效果 - 增强视觉

整个实现过程其实并不复杂,关键是要理解geo数据结构和简单的shader编程。下一期我们继续实现区域客流OD和实时公交功能。

代码仓库:zuo-wentao/bmap-demo: bmap demp

相关推荐
崔庆才丨静觅10 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅11 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅12 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊12 小时前
jwt介绍
前端
爱敲代码的小鱼12 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax