仰望不如掌握:Cesium 实现全球航班动态展示
发现了 OpenSky 提供的一个全球航班实时数据接口,刚好结合 Cesium 强大的三维可视化能力,尝试实现一个全球航班信息的可视化地球场景。
先来看下效果:
数据获取
我们首先从 https://opensky-network.org/api/states/all
获取数据。
这是 OpenSky 提供的 获取全球飞机实时状态信息的接口,可以返回当前在空中飞行的所有飞机的相关数据。获取的方式可以通过 fetch 就行。
格式大致如下:
主要包括两个字段:
{
"time": 1619191890,
"states": [
[
"3c6444", // 0: ICAO 24-bit address
"DLH7AB", // 1: 航班号(callsign)
"Germany", // 2: 所属国家
1619191889, // 3: 最近一次发送时间(Unix 时间戳)
1619191889, // 4: 最近一次接收时间
7.484, // 5: 经度
51.289, // 6: 纬度
10000.0, // 7: 高度(米)
false, // 8: 是否在地面
200.0, // 9: 速度(米/秒)
180.0, // 10: 航向(角度)
-3.5, // 11: 垂直速度(米/秒)
null, null, // 12-13: 传感器信息
51.4, // 14: 地面纬度
7.5, // 15: 地面经度
null, // 16: 地面高度
false // 17: transponder alert(一般无用)
],
...
]
}
鉴于开发过程中频繁请求可能导致接口限制,这里将返回结果保存到了本地 data.json
文件中,供后续测试与开发使用。
代码开发
下面展示核心部分的示例代码,主要流程为:
- 初始化 Cesium 场景。
- 从
data.json
加载并解析数据,滤除无效或不在指定范围内的航班数据。 - 根据数据生成 Cesium 实体并添加到场景中。
- 使用不同的颜色区分不同海拔高度的飞机模型。
初始化 Cesium 场景
// 配置 Cesium Ion 访问令牌
Cesium.Ion.defaultAccessToken = import.meta.env.VITE_CESIUM_ION_TOKEN;
// 创建 Cesium Viewer 实例
const viewer = new Cesium.Viewer(container);
Cesium.Viewer
是 Cesium 提供的封装类,内部会初始化一个场景(Scene)、地图控件(Globe)、UI 部件(如导航、动画条)等。
执行后,你就能看到一个基础的地球场景界面。
加载并筛选数据
这步骤我们需要处理
- 获取 json 的数据
- 根据数据做一下筛选,因为加载所有的飞机数据会导致地图卡顿,这里只用展示亚洲地区的飞机数据。亚洲地区的大致经纬度范围定义如下:
const ASIA_BOUNDS = {
west: 25.0, // 最西经度(中东地区)
east: 150.0, // 最东经度(亚洲东部)
south: 0.0, // 最南纬度(东南亚南部)
north: 60.0, // 最北纬度(俄罗斯亚洲部分)
};
获取与筛选数据的核心方法如下:
async function fetchAirplaneData() {
// 使用绝对路径或import.meta.url来确保正确加载data.json
// 方法1:使用import.meta.url获取当前模块的URL,然后构建相对路径
const baseUrl = new URL(".", import.meta.url).href;
const dataUrl = new URL("data.json", baseUrl).href;
const response = await fetch(dataUrl);
const data = await response.json();
// 筛选只在亚洲地区的飞机数据
const asiaStates = data.states
? data.states.filter(airplane) => {
const longitude = airplane[5]; // 经度
const latitude = airplane[6]; // 纬度
// 检查经纬度是否在亚洲范围内
return (
longitude !== null &&
latitude !== null &&
longitude >= ASIA_BOUNDS.west &&
longitude <= ASIA_BOUNDS.east &&
latitude >= ASIA_BOUNDS.south &&
latitude <= ASIA_BOUNDS.north
);
})
: [];
return asiaStates;
}
渲染飞机模型
Cesium 中,我们可通过 Entity 来添加自定义模型。下面通过一个函数 createAirplaneEntity
,基于一条航班信息添加对应的飞机模型。
function createAirplaneEntity(viewer, airplaneData) {
// 飞机数据格式: [icao24, callsign, origin_country, time_position, last_contact, longitude, latitude, baro_altitude, on_ground, velocity, true_track, vertical_rate, sensors, geo_altitude, squawk, spi, position_source]
const icao24 = airplaneData[0]; // 飞机唯一标识
const callsign = airplaneData[1] ? airplaneData[1].trim() : "未知航班";
const country = airplaneData[2] || "未知国家";
const longitude = airplaneData[5]; // 经度
const latitude = airplaneData[6]; // 纬度
const altitude = airplaneData[7] || 0; // 高度(米)
const heading = airplaneData[10] || 0; // 航向(度)
// 获取飞机颜色
const airplaneColor = getAirplaneColor(altitude);
// 创建新的飞机实体
const entity = viewer.entities.add({
id: icao24,
name: `${callsign} (${country})`,
position: Cesium.Cartesian3.fromDegrees(longitude, latitude, altitude),
orientation: Cesium.Transforms.headingPitchRollQuaternion(
Cesium.Cartesian3.fromDegrees(longitude, latitude, altitude),
new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(heading), 0, 0)
),
// 飞机模型
model: {
uri: "https://raw.githubusercontent.com/CesiumGS/cesium/main/Apps/SampleData/models/CesiumAir/Cesium_Air.glb",
minimumPixelSize: 32,
maximumScale: 20000,
scale: 0.5,
colorBlendMode: Cesium.ColorBlendMode.MIX, // 改为MIX模式,让颜色更自然
colorBlendFactor: 0.7, // 颜色混合因子,调整模型原始颜色与设定颜色的混合比例
},
});
airplaneEntities[icao24] = entity;
return entity;
}
- position:表示模型在地球上的位置,需通过
Cesium.Cartesian3.fromDegrees(longitude, latitude, altitude)
将经纬度(WGS84 坐标)转换成笛卡尔坐标。 - orientation:用于设置实体朝向。这里使用
Cesium.Transforms.headingPitchRollQuaternion
,并传入Cesium.HeadingPitchRoll
对象来指定机头朝向(heading)、俯仰(pitch)、横滚(roll)角度。 - model:配置模型属性,比如
uri
是模型 glTF 文件的 URL,scale
可缩放模型,color
与colorBlendMode
用于给模型着色。
通过上面的代码,我们就可以看到一个小飞机了
数据和模型的结合
有了数据和模型,现在根据航班数据进行循环来在具体的位置渲染对应的飞机。
// 更新或创建飞机实体
for (const airplane of airplaneData) {
const icao24 = airplane[0];
if (icao24) {
// 创建模型
createAirplaneEntity(viewer, airplane);
}
}
执行完毕后,你就能在亚洲部分区域看到对应的飞机模型。
根据高度渲染不同颜色
为了让不同飞行高度的飞机在地图上更醒目,可以通过设置模型的 color
属性来区分它们。示例逻辑如下:
function getAirplaneColor(altitude) {
// 使用更加美观的颜色方案
if (altitude > 10000) {
// 高空飞行 - 靓丽的深蓝色
return new Cesium.Color(0.0, 0.4, 0.8, 1.0);
} else if (altitude > 5000) {
// 中高空飞行 - 优雅的紫色
return new Cesium.Color(0.6, 0.2, 0.8, 1.0);
} else if (altitude > 2000) {
// 中低空飞行 - 温暖的橙色
return new Cesium.Color(1.0, 0.6, 0.0, 1.0);
} else {
// 低空飞行 - 鲜艳的红色
return new Cesium.Color(0.9, 0.2, 0.2, 1.0);
}
}
// 更新上面我们创建实体的代码,设置color属性
function createAirplaneEntity(viewer, airplaneData) {
...
const airplaneColor = getAirplaneColor(altitude)
// 创建并返回一个 Entity 实体
return viewer.entities.add({
model: {
...
color: airplaneColor,
...
},
});
}
你会在亚洲地区看到分布的飞机模型,并根据飞行高度呈现不同的颜色:
- 高空(大于 10,000 米):深蓝色
- 中高空(5,000 ~ 10,000 米):紫色
- 中低空(2,000 ~ 5,000 米):橙色
- 低空(小于 2,000 米):红色
代码
https://gitee.com/calmound/cesium-demo/tree/main/src/demos/fly (opens in a new tab)