跳到正文
已上线2026 年 9 月 —独立开发(数据 · 前端 · 后端)

巴士大屏 · 实时公交可视化

把公交线路、站点与班次时刻表快照,做成能在三维地图上任意时刻回放的画面。北京与成都两地,覆盖 3000+ 条线路。

巴士大屏 · 实时公交可视化 主图
技术栈
CesiumWebGLVue 3TypeScriptViteFastifySSESQLite微信小程序
标签
#Cesium#实时公交#数据可视化#微信小程序

亮点

  • 任意时刻回放:高峰实测同一秒钟有 8000+ 辆车在城市里跑,位置全部由 f(线路, 班次, 秒) 现场算出
  • 车辆沿真实道路行驶:稀疏的位置快照经曲线约束插值后贴路平滑运动,弯道不切角
  • 3Mbps 窄出口下服务 100 人并发:SSE 分片推送 + 预计算通道 + brotli 预压缩
  • 同一套前端同时承载网页与微信小程序两端,无需为小程序重写

这是什么

巴士大屏把一座城市的公交线网搬到三维地图上:线路、站点、班次,以及任意一个时刻城市里所有公交车的分布。

它不是「查某一班车到哪了」的查询工具,而是「看这座城市此刻在怎么运转」的观察窗口。所以在交互上做的是时间轴与视角,而不是线路搜索与到站预测。

目前覆盖北京与成都两地。

数据从哪里来(先说清楚)

这一点必须诚实交代,因为它决定了后面所有设计:

手上没有实时 GPS 数据流。 拿到的是一份静态快照 ——

  • 线路折线(GeoJSON,含上下行)
  • 站点表(含站序与所属线路)
  • 每条线路每个班次的发车时刻表

也就是说,「这辆车现在在哪」并不是被观测到的,而是算出来的。车辆位置是一个确定性函数:给定线路、班次与时刻,结果唯一。

这个前提带来两个后果,一个惊喜一个代价:

  • 惊喜:既然位置是算出来的,那就算「今天 08:30」和「明天 08:30」也没有区别 —— 于是任意时刻回放成了顺手的事,不需要任何额外基础设施。
  • 代价:画面每天长得一样。它不会因为「今天下雨晚点」而变化。所以定位只能是观察与欣赏,而不是查询。

宁可把它标成「可视化」,也不标成「实时查询」—— 这是这个项目里少数几个一开始就想清楚的事。

几个关键设计

车辆位置是纯函数。 f(线路, 班次, 秒) → 经纬度 + 航向。服务端把线路折线预处理好(累积弧长、站点弧长位置),运行期只做一次查表加插值。这样单机就能扛住「全城同一秒 8000+ 辆车」的计算量。

稀疏快照要贴路跑。 服务端按固定频率推送车辆位置,两次快照之间间隔十几到二十几米。直接线性插值会在弯道上切角、朝向抖动。做法是把快照投影到该车的线路折线上,在弧长维度插值 —— 细节写在《曲线约束插值》里。

窄出口的流量方案。 服务器出口只有 3Mbps,而前端包含 Cesium 引擎(原始体积约 4.3MB)。做法是三件事叠加:静态资源全量预压缩(brotli)、引擎资源与业务代码分离并走 CDN 或强缓存、车辆数据走 SSE 按视口网格分片推送而非全量广播。

一套前端,两端承载。 同样的代码既跑在浏览器里,也被微信小程序通过 web-view 承载,因此移动端的触摸手势、视口性能、生命周期的处理都必须在同一套代码里解决。

边界与不足

  • 不支持任意线路点选后看它的班次明细:时间轴是全局的,这是「大屏」定位下的刻意取舍。
  • 没有车号维度。原始数据里没有车辆编号,所以做不了「跟随某一辆车的一天」这类叙事。
  • 站名需要聚类。同名站点在不同线路上是不同记录,直接按名字合并会把相隔几公里的站合成一个点,必须按坐标聚类。
  • 区域聚合的数字是「此刻」的,切时间轴时同步变化,不做累计统计。

相关技术文章

本项目涉及的实现细节散在博客里:曲线约束插值、确定性位置函数、窄带宽下的并发方案。作品页只负责「是什么」,怎么做的在那边。

截图点击可放大 · 共 10 张

本项目的技术文章

本文含 5 个小节。