这个开源项目把飞机轨迹、野火、实时股价全塞进一个 WebGL 地球
做实时数据可视化的时候,最头疼的往往不是渲染——而是数据接入:多个 API 频率不同、协议不同(REST / WebSocket / SSE),怎么统一管理?新增一个数据源要不要改核心逻辑?
Crucix 是一个开源的全球情报仪表板,接入了 27 个公开数据源。

GitHub:https://github.com/calesthio/Crucix (opens in a new tab)
1. Globe.gl:比 CesiumJS 更轻量的 WebGL 地球
Crucix 的 3D 地球用的是 Globe.gl (opens in a new tab),不是 CesiumJS。
两者定位不同:
| Globe.gl | CesiumJS | |
|---|---|---|
| 底层 | Three.js 封装 | 自研渲染引擎 |
| 包体积 | 轻量 | 较重(地形/影像加载) |
| 上手难度 | 低,API 简单 | 高,概念多 |
| 适合场景 | 数据点/连线/热力图 | 专业 GIS、精确地形 |
| 大规模对象 | 一般 | 强(Primitive API) |
如果你只是想在地球上显示数据点、飞行路径、热力区域,Globe.gl 是更省力的选择。核心 API 极简:
import Globe from 'globe.gl';
const globe = Globe()
.globeImageUrl('//unpkg.com/three-globe/example/img/earth-night.jpg')
.pointsData(flightData)
.pointColor(() => 'orange')
.pointAltitude('altitude')(document.getElementById('globe'));学习价值:如果你一直觉得 CesiumJS 太重、入门太慢,Globe.gl 是很好的起点,Crucix 的源码里有完整的真实用法。
2. 模块化数据源设计:可插拔架构的参考实现
Crucix 最值得参考的是它的架构设计——每个数据源是一个独立模块,核心系统不感知数据来自哪里。
目录结构大概是这样:
src/
modules/
aviation/ # OpenSky Network 航班数据
maritime/ # AIS 海事数据
wildfire/ # NASA FIRMS 野火数据
radiation/ # 辐射监测数据
economics/ # FRED/财政部/BLS
satellite/ # 卫星轨道
finance/ # Yahoo Finance
...(共27个模块)每个模块实现相同的接口:
// 每个模块导出标准结构
export default {
name: 'aviation',
fetchInterval: 5000, // 刷新频率(各模块不同)
async fetch() { // 数据获取逻辑
const res = await fetch(OPENSKY_API);
return normalize(res); // 统一转换为标准格式
}
}核心调度器只负责按频率调用 fetch(),把结果推送给前端——完全不关心具体数据源的实现细节。
学习价值:这是"开闭原则"的真实应用。新增一个数据源只需新建一个模块文件,不改任何已有代码。自己做多数据源聚合项目时可以直接参考这个设计。
3. SSE 实时推送:比 WebSocket 更简单的单向实时方案
很多人遇到"需要实时更新"就直接上 WebSocket,但 Crucix 用的是 SSE(Server-Sent Events)。
两者的核心差别:
| SSE | WebSocket | |
|---|---|---|
| 通信方向 | 服务端 → 客户端(单向) | 双向 |
| 协议 | 普通 HTTP | ws:// 协议升级 |
| 实现复杂度 | 低 | 中 |
| 断线重连 | 浏览器自动处理 | 需要手动实现 |
| 适合场景 | 实时推送数据更新 | 需要客户端上行数据 |
情报仪表板只需要服务端持续推数据给浏览器,SSE 完全够用,代码也更简洁:
// 服务端(Express)
app.get('/stream', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
const send = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`);
const interval = setInterval(() => {
send(getLatestData());
}, 1000);
req.on('close', () => clearInterval(interval));
});
// 客户端
const es = new EventSource('/stream');
es.onmessage = (e) => updateGlobe(JSON.parse(e.data));学习价值:下次遇到"页面需要实时更新"的需求,先判断是否需要双向通信——如果只是服务端推数据,SSE 比 WebSocket 少写很多代码。
4. 三级告警系统:FLASH / PRIORITY / ROUTINE
Crucix 对接入的数据做了告警分级,根据异常程度自动触发不同级别:
- FLASH:最高优先级,异常事件(如辐射超标、大规模冲突)
- PRIORITY:中等优先级,值得关注但不紧急
- ROUTINE:日常数据更新通知
这套设计值得参考的地方是:告警逻辑写在每个数据模块里,而不是集中在一个判断函数里。各模块自己知道什么情况该触发什么级别,核心系统只负责路由和展示。
同时支持推送到 Telegram 和 Discord Bot,可以用命令查询:
/alerts # 查看当前活跃告警
/wildfire # 查询野火状态
/aviation # 查询航班异常从哪里开始读代码?
如果你想把 Crucix 作为学习素材:
- 先看
src/modules/下任意一个模块,理解数据源的标准接口 - 看主调度器,理解多模块如何统一调度
- 看 Globe.gl 相关代码,理解数据如何映射到 3D 地球上
- 看 SSE 推送部分,作为实时推送的实现参考
本地跑起来只需要:
git clone https://github.com/calesthio/Crucix.git
cd Crucix && cp .env.example .env
npm install && npm run dev
# 打开 http://localhost:3117大多数 Tier 1 数据源(航班、野火、海事)有免费公开 API,不配置 Key 也能看到效果。