vue2 使用 cesium 展示 TLE 星历数据
为啥突然写这么一篇文章呢,是因为我现在对 cesium 也是了解一些,在做一个项目的时候用到了,发现里面的东西还很多,稍微说一下。
环境准备
1. cesium包
项目中需要引入 cesium 包,我不是使用 npm 安装的,我是直接在官网下载的 cesium 包。下载完成后把包放进项目中使用的。

然后引入的话,只需要在 index.html 中引入一下就可以了。
在 <head> 标签里面放:
<link rel="stylesheet" href="./Cesium/Widgets/widgets.css">
在 <body> 标签最后面放:
<script type="text/javascript" src="./Cesium/Cesium.js"></script>
然后就完事了。cesium 也算引入成功了。
如果不放心的话,可以运行起 vue 项目,在控制台输入命令查看一下引入的 Cesium 版本:
console.log(Cesium.VERSION);

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

3. 其他
其他的话就是 moment,用来转换时间啥的,这个直接安装就行,想用就用,不想用就用 Date() 转,都可以。
Cesium 开发
首先我们创建一个 TCesium.js 文件,用来编写 Cesium 相关的代码:

当然,案例嘛,我文件随便创建了,但是正经开发的话需要做好目录规划。
初始化蓝星(地球)
首先我也不知道为啥把地球叫蓝星,很多人把地球称作蓝星,我也就这样叫吧。
1. 创建 TCesium 类
首先创建一个TCesium 类,在构造函数里面初始化蓝星。我尽可能把注释写的详细一些
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; // 不显示星空
}
}
然后蓝星初始化完成了就,我们需要在使用蓝星的组件编写一下基础结构引入一下就可以了。
<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对象的形式,就像下面:
{
"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:00 到 2025-10-10 15:00:00 这三个小时的卫星轨迹。或者是看一下现在时间到未来两个小时内的卫星运行轨迹。
准备数据
我先贴代码,然后一点一点解释哈:
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数据的函数
}
这个函数就是准备了一下星历数据,当然我这里写死了,到时候项目肯定后端返回了。
然后创建了startTime 和 endTime ,表示仿真这个时间段的卫星轨迹。
然后就是去走loadTleFile()函数。
加载TLE数据函数
我先写代码:
// 加载星历文件,实现卫星轨迹显示
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()函数:
start = Cesium.JulianDate.fromDate(this.beijingTimeToDate(startTime));
stop = Cesium.JulianDate.fromDate(this.beijingTimeToDate(endTime));
beijingTimeToDate()函数可以把北京时间字符串转成Date类型的对象,然后通过Cesium.JulianDate.fromDate()函数可以转成UTC时间。
// 北京时间字符串转换为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()函数 分批处理星历数据
贴这个函数完整代码,后面解释:
// 分批加载卫星数据
_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 处理星历函数
首先还是贴代码:
// 添加单个卫星实体
_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 库里面的,需要在顶部引入一下:
import { twoline2satrec, propagate, gstime, eciToGeodetic, degreesLong } from 'satellite.js'
这几个都是后面用到的,全写上吧。
第四个字段,leadTime: parseInt(24 * 3600 / satellite.tle2.slice(52, 64)) 用来计算卫星的轨道周期,单位是秒,表示卫星绕地球一周所需的时间。
这个satelliteInfo 对象解释完了,后面可能用到里面的字段,注意知道每个字段的含义就行。
在后面就是创建一个卫星实体cesiumSateEntity,代码注释的很清楚了,再就是把卫星实体对象添加到卫星数据源里面,其中添加到卫星数据源之后会返回这个卫星实体对象。
// 将卫星添加到卫星数据源
let satelliteEntity = this.satelliteDataSource.entities.add(new Cesium.Entity(cesiumSateEntity));
他会返回一个satelliteEntity ,你可以通过修改satelliteEntity 的内容,页面上的卫星状态也会跟着改变,比如颜色、是否显示轨迹、是否可见啥的,也可以获取他的信息,比如经度、纬度啥的。所以后边在对象里面存储了一下:
this.trackEntities[satelliteEntity.id] = satelliteEntity; // 存储卫星实体
主要说的是啥呢!是创建卫星实体对象的时候设置位置调用了一个专用的计算函数:
// 使用专用函数计算位置,传入卫星信息
position: this._getSatellitePositionProperty(start, satelliteInfo),
调用的_getSatellitePositionProperty函数才是关键,他用来计算这个卫星在这个仿真时间段的位置信息!
卫星位置计算
还是哈,先贴代码:
// 为单个卫星计算位置属性
_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 中用于描述随时间变化的位置的专用属性),供卫星实体绑定轨迹或动态更新位置使用。
对这个函数重点解释一下:
const positionProperty = new Cesium.SampledPositionProperty(); // 位置属性
这行代码是用来初始化位置属性,创建 SampledPositionProperty 实例,用于存储 “时间点 - 位置” 的映射关系,是 Cesium 中描述动态位置的核心对象(支持插值计算,使轨迹平滑)。
const baseTime = Cesium.JulianDate.toDate(start).getTime(); // 开始时间的时间戳
baseTime 是用来做基准时间的。就是仿真开始时间,这里转换成了时间戳。将 Cesium 的 JulianDate 格式起始时间(start)转换为 JavaScript 时间戳(毫秒级),方便后续计算采样时间。
const sampleInterval = this.timeInterval; // timeInterval 秒钟一个采样点(秒)
const totalSamples = Math.ceil(this.totalTime / sampleInterval); // 计算采样点数
上面这两行主要用来设置采样参数。 sampleInterval是每次采样的时间间隔(单位:秒),控制轨迹的精度(间隔越小,轨迹越精细,但性能消耗越高);totalSamples是根据总时长(this.totalTime)和间隔计算需要的采样点数量,确保覆盖整个时间段。
for (let i = 0; i < totalSamples; i++) { ... }
for循环的话,就是为了遍历每个采样点,计算对应时间的卫星位置。
然后是for循环里面的逻辑:
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,单位通常为千米)。
可以理解通过这两行代码,最后获得了这个在某个时间点下,该卫星在惯性坐标系下的位置信息。
继续:
if (!sateCoord?.x || !sateCoord?.y || !sateCoord?.z) { // 如果计算出的位置为空,则跳过
continue
}
上面这个判断是为了过滤无效位置,若轨道计算失败(返回 NaN 或 undefined),跳过当前采样点,避免错误数据影响轨迹。
再往下就是:
let gsmt = gstime(currentTime); // 计算当前时间的格林尼治恒星时(GST)
let efgd = eciToGeodetic(sateCoord, gsmt); // ECI 转大地坐标
gstime(currentTime)是用来计算当前时间的格林尼治恒星时(用于 ECI 到地固坐标的转换,消除地球自转影响)。
eciToGeodetic是将惯性系(ECI)坐标转换为大地坐标(经度、纬度、高度,基于 WGS84 椭球),即卫星在地面观测者眼中的位置。
let lon = degreesLong(efgd.longitude); // 经度(弧度→度)
let lat = degreesLong(efgd.latitude); // 纬度(弧度→度)
let height = efgd.height; // 高度(千米)
上面是将大地坐标单位转换,将经纬度从弧度转换为度(Cesium 常用单位),保留高度值(后续需转换为米)。
再往下是:
let stkWorldPoint = Cesium.Cartesian3.fromDegrees(lon, lat, height * 1000);
讲位置信息转换为 Cesium 世界坐标。Cesium.Cartesian3.fromDegrees作用是将经纬度(度)和高度(米,因此 ×1000 转换千米→米)转换为 Cesium 的地固坐标系(ECEF) 坐标(Cartesian3 对象),即三维世界中的位置。
最后了:
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函数:
// 为单个卫星计算位置属性
_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不是更好吗?我想说的是,在实际开发中会遇到各种离谱的事情,让人不得不放弃一些东西,毕竟选择的实现方式还是以实际开发后出结果为准,加油吧各位!希望对大家有用!
好了今天就先到这儿!