# vue2 使用 cesium 展示 TLE 星历数据

vue2 使用 cesium 展示 TLE 星历数据

为啥突然写这么一篇文章呢,是因为我现在对 cesium 也是了解一些,在做一个项目的时候用到了,发现里面的东西还很多,稍微说一下。

环境准备

1. cesium包

项目中需要引入 cesium 包,我不是使用 npm 安装的,我是直接在官网下载的 cesium 包。下载完成后把包放进项目中使用的。

然后引入的话,只需要在 index.html 中引入一下就可以了。

<head> 标签里面放:

html 复制代码
<link rel="stylesheet" href="./Cesium/Widgets/widgets.css">

<body> 标签最后面放:

html 复制代码
<script type="text/javascript" src="./Cesium/Cesium.js"></script>

然后就完事了。cesium 也算引入成功了。

如果不放心的话,可以运行起 vue 项目,在控制台输入命令查看一下引入的 Cesium 版本:

js 复制代码
console.log(Cesium.VERSION);

2. satellite.js

这个的话我就是使用 npm 安装的了,我安装的 5.0 版本以上的,最好是5这个版本往上,不要装4,API可能会有差异。

3. 其他

其他的话就是 moment,用来转换时间啥的,这个直接安装就行,想用就用,不想用就用 Date() 转,都可以。

Cesium 开发

首先我们创建一个 TCesium.js 文件,用来编写 Cesium 相关的代码:

当然,案例嘛,我文件随便创建了,但是正经开发的话需要做好目录规划。

初始化蓝星(地球)

首先我也不知道为啥把地球叫蓝星,很多人把地球称作蓝星,我也就这样叫吧。

1. 创建 TCesium 类

首先创建一个TCesium 类,在构造函数里面初始化蓝星。我尽可能把注释写的详细一些

js 复制代码
export class TCesium {

  dom = null,  // dom节点对象
  scene = null;  // 当前场景
  viewer = null;  // 当前地图对象
  trackEntities = {} // 卫星对象
  totalTime = 24*60*60   // 仿真总时长,默认一天, 单位秒
  timeInterval = 60  // 采样间隔,单位秒
  satelliteDataSource = null  // 卫星数据源

	// 接收一个dom,就是用来渲染蓝星的Dom
  constructor(dom) {
    this.dom = dom;  // 把穿进的dom节点赋值给全局
    Cesium.Ion.defaultAccessToken = '这个地方需要填写自己在官网申请的Token值';   // 设置 Token,需要从官网自己申请
    this.viewer = new Cesium.Viewer(dom, {
      homeButton: false,  // 显示主页按钮
      sceneModePicker: true,   // 是否显示切换2D/3D按钮
      baseLayerPicker: false,  // 图层切换按钮
      animation: true,  // 动画,需要开启
      infoBox: false,  // 信息框显示
      selectionIndicator: false, 
      geocoder: false,
      timeline: true,  // 时间轴,要开启的啊
      fullscreenButton: false,
      shouldAnimate: false,
      navigationHelpButton: false,
      terrainProvider: new Cesium.CesiumTerrainProvider({   // 基础地形数据
        url: 'https://www.supermapol.com/realspace/services/3D-stk_terrain/rest/realspace/datas/info/data/path',
        requestVertexNormals: true
      }),
    })
    this.scene = this.viewer.scene;
    this.viewer.scene.postProcessStages.fxaa.enabled = true   // 开启抗锯齿优化
    this.viewer.scene.sun.show = false;   // 不显示太阳
    this.viewer.scene.moon.show = false;   // 不显示月亮
    this.viewer.scene.skyBox.show = false;  // 不显示星空
  }
}

然后蓝星初始化完成了就,我们需要在使用蓝星的组件编写一下基础结构引入一下就可以了。

html 复制代码
<template>
  <div class="app">
    <div class="ed-earth-model" id="earthModel" ref="earthModel"></div>
  </div>
</template>
<script>
import { TCesium } from "./TCesium";
export default {
  name: "Home",

  data() {
    return {
      cesium: null
    }
  },

  mounted() {
    this.$nextTick(() => {
      this.initMap();  // 初始化地图
    })
  },

  methods: {
    initMap() {
      if (!this.cesium) {
        this.cesium = new TCesium(this.$refs.earthModel);
      }
    }
  }
};
</script>
<style scoped>
.app {
  position: relative;
  width: 100%;
  height: 100%;
}

.ed-earth-model {
  width: 100%;
  height: 100%;
}
</style>

编写完上面的代码就可以看到蓝星初始化完成了。

非常好!完美~

2. 初始化数据

我们写一个方法用来初始化数据,加载TLE星历数据的话,我们可以从网上找一些星历数据,或者客户提供星历数据,他们提供的星历数据一般是这个格式的:

每个卫星的星历包含三行,然后如果有多个的话就往下排,这个星历文件就是简单的 txt 文件。

然后我们需要把这个星历文件转换成JSON对象的形式,就像下面:

josn 复制代码
{
  "name": "STARLINK-2589",
  "tle1": "1 48371U 21038U   25270.54822457  .00018630  00000+0  12665-2 0  9999",
  "tle2": "2 48371  53.0562   3.9906 0001373 107.0633 253.0506 15.06394038242577"
},

这个可以写个JS函数进行转换,我写了,但是我不想浪费时间贴了,这个不是重点,到时候你们需要的话直接自己写一个就行了。

星历数据自己去找就行,我上面的都不一定准确了,可以去网站上去复制,很多网站可以查看全球登记在册的卫星,都可以复制星历数据,要保证星历数据的准确性哈,出一点问题都不行,星历数据有了的话,然后我们就可以在地球展示轨道了。

但是有一点需要说一下,就是这个星历啊,他是有时效性的,一般时效性就是7天到14天左右,太久了的话不是不能用,而是不准确了,卫星的位置可能天差地别了。

3. 卫星仿真

首先仿真这个就是看卫星在某一时间的运行轨迹嘛,我们写一个方法,然后来处理这个逻辑。

首先仿真时间可以是自定义仿真时间,也可以是使用当前时间开始仿真。

比如根据提供的星历数据,看一下 2025-10-10 12:00:002025-10-10 15:00:00 这三个小时的卫星轨迹。或者是看一下现在时间到未来两个小时内的卫星运行轨迹。

准备数据

我先贴代码,然后一点一点解释哈:

js 复制代码
  addTle() {
    let tleData = [   // 星历数据
      {
        "name": "STARLINK-2589",
        "tle1": "1 48371U 21038U   25270.54822457  .00018630  00000+0  12665-2 0  9999",
        "tle2": "2 48371  53.0562   3.9906 0001373 107.0633 253.0506 15.06394038242577"
      }
    ]
    let startTime = "2025-11-11 08:00:00";  // 东八区时间,也就是北京时间
    let endTime = "2025-11-11 12:00:00";  // 东八区时间,也就是北京时间
    this.loadTleFile(tleData, true, startTime, endTime);  // 调用加载TLE数据的函数
  }

这个函数就是准备了一下星历数据,当然我这里写死了,到时候项目肯定后端返回了。

然后创建了startTimeendTime ,表示仿真这个时间段的卫星轨迹。

然后就是去走loadTleFile()函数。

加载TLE数据函数

我先写代码:

js 复制代码
  // 加载星历文件,实现卫星轨迹显示
  loadTleFile(tleData, showPath = false, startTime = null, endTime = null) {
    let start = "";
    let stop = "";
    if (startTime && endTime) { // 假设提供了开始时间和结束时间
      start = Cesium.JulianDate.fromDate(this.beijingTimeToDate(startTime));
      stop = Cesium.JulianDate.fromDate(this.beijingTimeToDate(endTime));
    } else {  // 如果没有传时间默认从系统当前时间往后一天的仿真时间
      start = Cesium.JulianDate.fromIso8601(new Date().toISOString());
      stop = Cesium.JulianDate.addSeconds(start, this.totalTime, new Cesium.JulianDate());
    }
    this.totalTime = Cesium.JulianDate.secondsDifference(stop, start);  // 计算开始时间和结束时间的时间差 单位秒
    this.viewer.clock.startTime = start.clone() // 给cesium时间轴设置开始的时间,也就是上边的东八区时间
    this.viewer.clock.stopTime = stop.clone() // 设置cesium时间轴设置结束的时间
    this.viewer.clock.currentTime = start.clone() // 设置cesium时间轴设置当前的时间
    this.viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP // 时间结束了,再继续重复来一遍
    this.viewer.clock.multiplier = 1 // 时间轴倍速,1是正常速度,2是两倍速,0.5是半速
    this.viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER // 时间轴步长,SYSTEM_CLOCK_MULTIPLIER是根据系统时间步长来更新时间轴,其他选项还有TICK_DEPENDENT和SYSTEM_CLOCK
    this.viewer.timeline.updateFromClock();  // 强制更新时间轴
    this.viewer.timeline.zoomTo(start.clone(), stop.clone());  // 设置时间轴显示区间

    if (!this.satelliteDataSource) {  // 判断卫星数据源是不是空(可以不用,视情况而定,写一下是知道可以这样用)
      this.satelliteDataSource = new Cesium.CustomDataSource('satellite')  // 是空则创建一个数据源
      this.viewer.dataSources.add(this.satelliteDataSource)  // 把数据源添加到viewer,后期卫星全部添加到卫星数据源
    }
    // 分批处理卫星数据,避免一次性处理所有卫星导致页面卡住
    let sss = JSON.parse(JSON.stringify(tleData))
    this._loadSatellitesInBatches(sss, start, stop, 10, showPath) // 每批加载10个卫星
  }

首先loadTleFile()函数接收四个参数,分别是星历数据列表是否显示卫星轨迹线仿真开始北京时间仿真结束北京时间

里面主要做了啥呢?主要就是设置时间轴,让Cesium界面上的时间轴显示成我们设置的仿真时间段

这里有一问题需要特别注意一下哈,那就是时间问题。我们传入的开始时间和结束时间是北京时间,也就是东八区的时间。但是!Cesium时间轴设置的时间需要是UTC时间

UTC 时间(Coordinated Universal Time,协调世界时)是全球通用的标准时间,被世界各地用作时间基准,其核心特点是统一、无时区偏移、基于原子时和地球自转校准。

所以说我们设置时间轴的时候需要把北京时间转换成UTC时间

我们使用了beijingTimeToDate()函数:

js 复制代码
      start = Cesium.JulianDate.fromDate(this.beijingTimeToDate(startTime));
      stop = Cesium.JulianDate.fromDate(this.beijingTimeToDate(endTime));

beijingTimeToDate()函数可以把北京时间字符串转成Date类型的对象,然后通过Cesium.JulianDate.fromDate()函数可以转成UTC时间。

js 复制代码
  // 北京时间字符串转换为Date对象
  beijingTimeToDate(beijingTimeString) {
    const isoString = beijingTimeString.replace(' ', 'T');
    return new Date(isoString);
  }

解释一下,其实北京时间和UTC时间差了八小时,也就是说北京时间比UTC时间多八小时。比如北京时间2025-11-11 08:00:00 对应的UTC时间就是 2025-11-11 00:00:00

在后面就是设置时间轴了,更新时间轴,这样的话,我们时间轴就设置成功了。

设置成功就调用了 _loadSatellitesInBatches() 函数分批处理星历数据了。

_loadSatellitesInBatches()函数 分批处理星历数据

贴这个函数完整代码,后面解释:

js 复制代码
  // 分批加载卫星数据 
  _loadSatellitesInBatches(satellites, start, stop, batchSize, showPath) {
    const batches = [];
    // 将卫星数据分成多个批次
    for (let i = 0; i < satellites.length; i += batchSize) {
      batches.push(satellites.slice(i, i + batchSize));
    }
    // 递归处理每个批次
    const processBatch = (batchIndex) => {
      if (batchIndex >= batches.length) {
        return; // 所有批次处理完毕
      }
      // 处理当前批次
      const currentBatch = batches[batchIndex];
      currentBatch.forEach(satellite => {
        this._addSatelliteEntity(satellite, start, stop, showPath);
      });
      // 在下一帧处理下一批次,避免阻塞主线程
      requestAnimationFrame(() => {
        processBatch(batchIndex + 1);
      });
    };
    // 开始处理第一批
    processBatch(0);
  }

为啥要分批处理哈,其实单纯案例的话不需要,分批是怕卫星星历很多,比如上百颗卫星,或者是上千颗卫星,最好分批。

因为后面需要对每颗卫星的星历进行处理,获取每颗卫星不同时间段的位置,比如1000颗卫星,每颗卫星取1000个位置,这个遍历时间是有点儿久的,如果不分批的话,Cesium展示的进程会被卡死,页面整个操作不了了,但是案例就一颗卫星,不需要分批,但是做分批处理是没坏处的,有备无患。

_loadSatellitesInBatches()函数接收五个参数,分别是星历列表开始UTC时间结束UTC时间每批个数是否显示轨迹线

这个函数没啥好说的,看看就懂,跟Cesium没啥关系,就是分批加载,一帧加载十颗卫星。

每一帧遍历这一批次的卫星星历,执行一个this._addsatelliteEntity(satellite, start, stop, showPath); 函数,这个函数才是真正的处理星历的函数。

_addSatelliteEntity 处理星历函数

首先还是贴代码:

js 复制代码
  // 添加单个卫星实体
  _addSatelliteEntity(satellite, start, stop, showPath) {
    // 创建临时对象存储当前卫星信息,避免修改this对象
    const satelliteInfo = {
      name: satellite.name.trim(),
      tleLine1: satellite.tle1.trim(),
      tleLine2: satellite.tle2.trim(),
      satrec: twoline2satrec(satellite.tle1.trim(), satellite.tle2.trim()),
      leadTime: parseInt(24 * 3600 / satellite.tle2.slice(52, 64))
    };

    // 创建卫星实体
    let cesiumSateEntity = {
      id: satellite.noradId || satellite.name,  // 设置卫星对象的ID
      name: satellite.name, // 设置卫星的名称
      description: satellite.name,  // 设置描述
      // 使用专用函数计算位置,传入卫星信息
      position: this._getSatellitePositionProperty(start, satelliteInfo),
      point: {  // 卫星用点表示
        pixelSize: 10,  // 卫星点十个像素
        color: Cesium.Color.WHITE,  // 卫星点是白色的
      },
      path: {  // 卫星轨迹线设置
        width: 0.5,   // 轨迹线的宽度,单位是像素
        leadTime: satelliteInfo.leadTime,  //  轨迹线 "前瞻时间",即显示卫星从当前时间开始,未来一段时间内的预测轨迹长度,这里设置的就是卫星运行一圈的时间,也就是显示未来一圈的轨迹
        trailTime: 0,  // 轨迹线 "回溯时间",即显示卫星从过去一段时间到当前时间的轨迹长度,设置为 0 表示不显示卫星过去的轨迹,只展示未来的预测轨迹
        material: Cesium.Color.YELLOW,  // 线是黄色的
        show: showPath === true ? true : false, // 默认隐藏轨迹
        clampToGround: false  // 轨迹线是否 "贴地",卫星在天上飞,轨迹就不要贴地了哈
      },
      label: {  // 卫星的label展示配置
        show: true,  // 是否显示
        text: satellite.name,  // 显示的内容
        font: '12px sans-serif',   // 字体设置
        fillColor: Cesium.Color.WHITE,   // 填充颜色白色
        outlineColor: Cesium.Color.BLACK,  // 边框颜色黑色
        outlineWidth: 2,  // 边框宽度2像素
        pixelOffset: new Cesium.Cartesian2(0, 15),  // 偏移量
      }
    };
    // 添加卫星实体
    let satelliteEntity = this.satelliteDataSource.entities.add(new Cesium.Entity(cesiumSateEntity));
    this.trackEntities[satelliteEntity.id] = satelliteEntity;
  }

首先这个函数接收四个参数:单颗卫星星历数据仿真开始UTC时间仿真结束UTC时间是否显示卫星轨迹线

函数第一步是对卫星 TLE 数据的结构化封装:

因为传进来的satellite其实就是单个卫星数据,就是这个:

所以satelliteInfo 前三个值不解释了。

然后第三个字段,satrec: twoline2satrec(satellite.tle1.trim(), satellite.tle2.trim()) 是通过 twoline2satrec 方法(通常来自 satellite.js 库)解析 TLE 数据后得到的 卫星轨道模型对象(satrec)satrec 包含了 SGP4 轨道计算所需的所有参数(如半长轴、倾角、历元时间等),是后续调用 propagate 方法计算卫星实时位置(ECI 坐标)的核心输入。

twoline2satrec 方法是satellite.js 库里面的,需要在顶部引入一下:

js 复制代码
import { twoline2satrec, propagate, gstime, eciToGeodetic, degreesLong } from 'satellite.js'

这几个都是后面用到的,全写上吧。

第四个字段,leadTime: parseInt(24 * 3600 / satellite.tle2.slice(52, 64)) 用来计算卫星的轨道周期,单位是秒,表示卫星绕地球一周所需的时间。

这个satelliteInfo 对象解释完了,后面可能用到里面的字段,注意知道每个字段的含义就行。

在后面就是创建一个卫星实体cesiumSateEntity,代码注释的很清楚了,再就是把卫星实体对象添加到卫星数据源里面,其中添加到卫星数据源之后会返回这个卫星实体对象。

js 复制代码
// 将卫星添加到卫星数据源
let satelliteEntity = this.satelliteDataSource.entities.add(new Cesium.Entity(cesiumSateEntity));

他会返回一个satelliteEntity ,你可以通过修改satelliteEntity 的内容,页面上的卫星状态也会跟着改变,比如颜色、是否显示轨迹、是否可见啥的,也可以获取他的信息,比如经度、纬度啥的。所以后边在对象里面存储了一下:

js 复制代码
this.trackEntities[satelliteEntity.id] = satelliteEntity; // 存储卫星实体

主要说的是啥呢!是创建卫星实体对象的时候设置位置调用了一个专用的计算函数:

js 复制代码
  // 使用专用函数计算位置,传入卫星信息
  position: this._getSatellitePositionProperty(start, satelliteInfo),

调用的_getSatellitePositionProperty函数才是关键,他用来计算这个卫星在这个仿真时间段的位置信息!

卫星位置计算

还是哈,先贴代码:

js 复制代码
  // 为单个卫星计算位置属性
  _getSatellitePositionProperty(start, satelliteInfo) {
    const positionProperty = new Cesium.SampledPositionProperty();  // 位置属性
    const baseTime = Cesium.JulianDate.toDate(start).getTime();  // 开始时间的时间戳
    // 这样可以在保持轨迹连续性的同时大幅提高性能
    const sampleInterval = this.timeInterval; // timeInterval 秒钟一个采样点(秒)
    const totalSamples = Math.ceil(this.totalTime / sampleInterval); // 计算采样点数
    for (let i = 0; i < totalSamples; i++) {  // 遍历每个采样点
      const currentTime = new Date(baseTime + i * sampleInterval * 1000);  // 采样时间
      const sateCoord = propagate(satelliteInfo.satrec, currentTime).position;  // 计算当前时间的位置
      if (!sateCoord?.x || !sateCoord?.y || !sateCoord?.z) {  // 如果计算出的位置为空,则跳过
        continue
      }
      let gsmt = gstime(currentTime)  // 计算当前时间的 GST 时间
      let efgd = eciToGeodetic(sateCoord, gsmt)  //  将 ECI 坐标转换为大地坐标
      let lon = degreesLong(efgd.longitude)  // 将大地坐标转换为度
      let lat = degreesLong(efgd.latitude)  //  将大地坐标转换为度
      let height = efgd.height  //  高度
      let stkWorldPoint = Cesium.Cartesian3.fromDegrees(lon, lat, height * 1000)  // 将大地坐标转换为 STK 世界坐标
      const cesiumTime = Cesium.JulianDate.addSeconds(start, i * sampleInterval, new Cesium.JulianDate());
      positionProperty.addSample(cesiumTime, stkWorldPoint);
    }
    return positionProperty;
  }

这个函数 _getSatellitePositionProperty 是用于在 Cesium 中生成单个卫星的时间序列位置属性(SampledPositionProperty),核心功能是通过轨道计算获取卫星在一段时间内的连续位置,并将其转换为 Cesium 可识别的坐标格式,最终用于绘制卫星轨迹或动态展示卫星运动。

整体逻辑是:函数从指定的起始时间 start 开始,按固定时间间隔采样,计算卫星在每个采样时刻的空间位置,将这些 "时间 - 位置" 对整合为 SampledPositionProperty 对象(Cesium 中用于描述随时间变化的位置的专用属性),供卫星实体绑定轨迹或动态更新位置使用。

对这个函数重点解释一下:

js 复制代码
const positionProperty = new Cesium.SampledPositionProperty();  // 位置属性

这行代码是用来初始化位置属性,创建 SampledPositionProperty 实例,用于存储 "时间点 - 位置" 的映射关系,是 Cesium 中描述动态位置的核心对象(支持插值计算,使轨迹平滑)。

js 复制代码
const baseTime = Cesium.JulianDate.toDate(start).getTime();  // 开始时间的时间戳

baseTime 是用来做基准时间的。就是仿真开始时间,这里转换成了时间戳。将 Cesium 的 JulianDate 格式起始时间(start)转换为 JavaScript 时间戳(毫秒级),方便后续计算采样时间。

js 复制代码
const sampleInterval = this.timeInterval; // timeInterval 秒钟一个采样点(秒)
const totalSamples = Math.ceil(this.totalTime / sampleInterval); // 计算采样点数

上面这两行主要用来设置采样参数sampleInterval是每次采样的时间间隔(单位:秒),控制轨迹的精度(间隔越小,轨迹越精细,但性能消耗越高);totalSamples是根据总时长(this.totalTime)和间隔计算需要的采样点数量,确保覆盖整个时间段。

js 复制代码
for (let i = 0; i < totalSamples; i++) { ... }

for循环的话,就是为了遍历每个采样点,计算对应时间的卫星位置。

然后是for循环里面的逻辑:

js 复制代码
const currentTime = new Date(baseTime + i * sampleInterval * 1000);  // 采样时间
const sateCoord = propagate(satelliteInfo.satrec, currentTime).position;  // 计算当前时间的位置

currentTime 这个是啥呢,就是获取第 i 个采样点的时间,即:基准时间 + 第 i 个取样点 * 取样间隔时间(s) * 1000,也就是基于起始时间戳,累加 i * 采样间隔(毫秒),得到当前采样点的 UTC 时间(Date 对象)。

sateCoord 是调用 propagate 方法,根据卫星的 satrec 轨道模型和当前时间,计算卫星在惯性坐标系(ECI) 中的位置(x, y, z,单位通常为千米)。

可以理解通过这两行代码,最后获得了这个在某个时间点下,该卫星在惯性坐标系下的位置信息。

继续:

js 复制代码
if (!sateCoord?.x || !sateCoord?.y || !sateCoord?.z) {  // 如果计算出的位置为空,则跳过
  continue
}

上面这个判断是为了过滤无效位置,若轨道计算失败(返回 NaN 或 undefined),跳过当前采样点,避免错误数据影响轨迹。

再往下就是:

js 复制代码
let gsmt = gstime(currentTime); // 计算当前时间的格林尼治恒星时(GST)
let efgd = eciToGeodetic(sateCoord, gsmt); // ECI 转大地坐标

gstime(currentTime)是用来计算当前时间的格林尼治恒星时(用于 ECI 到地固坐标的转换,消除地球自转影响)。 eciToGeodetic是将惯性系(ECI)坐标转换为大地坐标(经度、纬度、高度,基于 WGS84 椭球),即卫星在地面观测者眼中的位置

js 复制代码
let lon = degreesLong(efgd.longitude); // 经度(弧度→度)
let lat = degreesLong(efgd.latitude);  // 纬度(弧度→度)
let height = efgd.height;              // 高度(千米)

上面是将大地坐标单位转换,将经纬度从弧度转换为度(Cesium 常用单位),保留高度值(后续需转换为米)。

再往下是:

js 复制代码
let stkWorldPoint = Cesium.Cartesian3.fromDegrees(lon, lat, height * 1000);

讲位置信息转换为 Cesium 世界坐标。Cesium.Cartesian3.fromDegrees作用是将经纬度(度)和高度(米,因此 ×1000 转换千米→米)转换为 Cesium 的地固坐标系(ECEF) 坐标(Cartesian3 对象),即三维世界中的位置。

最后了:

js 复制代码
const cesiumTime = Cesium.JulianDate.addSeconds(start, i * sampleInterval, new Cesium.JulianDate());
positionProperty.addSample(cesiumTime, stkWorldPoint);

绑定时间与位置,计算当前采样点对应的 Cesium 时间(JulianDate 格式),将 "时间 - 位置" 对添加到 positionProperty 中,完成一个采样点的记录。

最终返回包含所有采样点的 SampledPositionProperty 对象,可直接绑定到 Cesium 的卫星实体(Entity)上,用于动态展示卫星轨迹和位置

所以这个函数是卫星轨迹可视化的核心逻辑,通过 "时间采样→轨道计算→坐标转换→绑定属性" 的流程,将卫星的轨道参数(TLE)转换为 Cesium 可渲染的动态位置数据。

然后就可以看一下页面效果:

我们可以看到卫星轨迹线已经加载出来了。

然后我们用第三方软件加载一下这个星历,看一下效果是不是一样的:

我们可以看到我们绘制的轨迹线和第三方软件绘制的是一样的。这就完成了!

注意

有一点需要注意。

就是我们看3D的效果,卫星轨迹线为什么转完一圈之后首尾没有闭合啊?感觉像是轨迹线跑偏了一样,他理论上围着地球绕圈,不应该是个正圆吗?为什么还是"丝带"状缠起来了?我看别人做的是正圆。

这是一个很好的问题!

因为我们在_getSatellitePositionProperty函数中,计算卫星采样位置的时候,最后转换成了地固坐标系(ECEF)。而之前那些正圆,可以实现首尾相连的使用的是惯性坐标系(ECI)

看之前我们写的代码可以看出来,我们拿到惯性坐标后转成了地固坐标

那么 惯性坐标 和 地固坐标 的区别是什么?

惯性坐标(ECI)地固坐标都是用来描述卫星、航天器等空间物体位置的两种核心坐标系,核心区别在于是否随地球自转,适用场景也因此不同。

惯性坐标:地面上的固定点(如北京)的坐标会随地球自转而持续变化(每天绕 Z 轴旋转一圈);卫星的坐标变化仅由其自身轨道运动导致(如近地卫星沿椭圆轨道运动)。

地固坐标 :地面上的固定点(如北京)的坐标始终不变;卫星的坐标变化是其轨道运动与地球自转的 "叠加效果"(如卫星从西向东运动,同时地球也在自转)。

ECI 是 "宇宙视角":不随地球转,适合分析卫星的绝对轨道运动; ECEF 是 "地球视角":随地球转,适合描述物体相对于地面的位置。

那这样的话什么时候使用惯性坐标,什么时候使用地固坐标呢?

惯性坐标适用于:卫星轨道力学分析轨道设计航天器导航(需计算绝对运动)。卫星轨道预测(如用 TLE 计算 ECI 坐标)、星际航行轨道规划、天体力学仿真。

固地坐标适用于地面观测通信链路分析、地图匹配(需描述相对地球表面的位置)。卫星地面覆盖范围计算、地面站与卫星的方位角 / 仰角计算、GPS 等导航系统定位。

获取惯性坐标位置展示

那上面写了固地坐标展示的方式,下面写一下惯性坐标展示的方法,其实很简单,只需要修改_getSatellitePositionProperty函数:

js 复制代码
  // 为单个卫星计算位置属性
  _getSatellitePositionProperty(start, satelliteInfo) {
    const positionProperty = new Cesium.SampledPositionProperty();  // 位置属性
    const baseTime = Cesium.JulianDate.toDate(start).getTime();  // 开始时间的时间戳
    // 这样可以在保持轨迹连续性的同时大幅提高性能
    const sampleInterval = this.timeInterval;  // 采样时间间隔(秒)
    const totalSamples = Math.ceil(this.totalTime / sampleInterval); // 计算采样点数
    for (let i = 0; i < totalSamples; i++) {  // 遍历每个采样点
      const currentTime = new Date(baseTime + i * sampleInterval * 1000);  // 采样时间
      const sateCoord = propagate(satelliteInfo.satrec, currentTime).position;  // 计算当前时间的位置
      if (!sateCoord?.x || !sateCoord?.y || !sateCoord?.z) {  // 如果计算出的位置为空,则跳过
        continue
      }
      const cesiumTime = Cesium.JulianDate.addSeconds(start, i * sampleInterval, new Cesium.JulianDate());
      const cesiumPosition = {
        x: sateCoord.x * 1000,
        y: sateCoord.y * 1000,
        z: sateCoord.z * 1000
      }
      positionProperty.addSample(cesiumTime, cesiumPosition);
    }
    return positionProperty;
  }

这就可以了

这就是首尾相连的正圆了。

再说一点哈

很多人可能觉得这个方式绘制星历太麻烦了,性能还不好,然后使用czml不是更好吗?我想说的是,在实际开发中会遇到各种离谱的事情,让人不得不放弃一些东西,毕竟选择的实现方式还是以实际开发后出结果为准,加油吧各位!希望对大家有用!

好了今天就先到这儿!

相关推荐
宇余2 小时前
从 useState 到 URLState:前端状态管理的另一种思路
前端·vue.js
白兰地空瓶2 小时前
🚀 10 分钟吃透 CSS position 定位!从底层原理到避坑实战,搞定所有布局难题
前端·css
onthewaying3 小时前
在Android平台上使用Three.js优雅的加载3D模型
android·前端·three.js
冴羽3 小时前
能让 GitHub 删除泄露的苹果源码还有 8000 多个相关仓库的 DMCA 是什么?
前端·javascript·react.js
悟能不能悟3 小时前
jsp怎么拿到url参数
java·前端·javascript
程序猿小蒜3 小时前
基于SpringBoot的企业资产管理系统开发与设计
java·前端·spring boot·后端·spring
Mapmost3 小时前
零代码+三维仿真!实现自然灾害的可视化模拟与精准预警
前端
程序猿_极客3 小时前
JavaScript 的 Web APIs 入门到实战全总结(day7):从数据处理到交互落地的全链路实战(附实战案例代码)
开发语言·前端·javascript·交互·web apis 入门到实战
suzumiyahr3 小时前
用awesome-digital-human-live2d创建属于自己的数字人
前端·人工智能·后端