Three.js案例
全球航班动态展示

仰望不如掌握: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 文件中,供后续测试与开发使用。

代码开发

下面展示核心部分的示例代码,主要流程为:

  1. 初始化 Cesium 场景。
  2. data.json 加载并解析数据,滤除无效或不在指定范围内的航班数据。
  3. 根据数据生成 Cesium 实体并添加到场景中。
  4. 使用不同的颜色区分不同海拔高度的飞机模型。

初始化 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 部件(如导航、动画条)等。

执行后,你就能看到一个基础的地球场景界面。

加载并筛选数据

这步骤我们需要处理

  1. 获取 json 的数据
  2. 根据数据做一下筛选,因为加载所有的飞机数据会导致地图卡顿,这里只用展示亚洲地区的飞机数据。亚洲地区的大致经纬度范围定义如下:
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 可缩放模型,colorcolorBlendMode 用于给模型着色。

通过上面的代码,我们就可以看到一个小飞机了

数据和模型的结合

有了数据和模型,现在根据航班数据进行循环来在具体的位置渲染对应的飞机。

// 更新或创建飞机实体
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)