在航天、通信等领域,3D 可视化技术能够将复杂的卫星轨道、通信链路、地面设施等数据直观呈现,极大提升管理效率和决策准确性。本文将带大家基于 Cesium 引擎,从零构建一套激光网络管理可视化系统,实现卫星轨道模拟、星地 / 星间通信链路动态展示、多轨道切换等核心功能,深入解析 Cesium 在空间数据可视化中的实践应用。
一、系统效果预览
本系统实现了以下核心功能:
- 地球底图加载(天地图影像 + 路网注记),支持星空、太阳、月亮等天文要素显示
- 多轨道卫星管理(高轨、倾斜轨、近地轨),支持轨道切换和相机自动适配
- 卫星轨道实时计算与飞行模拟,基于 TLE 数据精准还原卫星运行轨迹
- 星间、星地(卫星 - 雷达)、地地(雷达 - 雷达)通信链路动态渲染,支持通断状态和业务质量展示
- 交互功能:鼠标双击聚焦雷达、右键查看卫星 / 链路信息、时间轴控制播放速度
- 信息面板:实体点击弹窗展示详情,支持拖拽调整位置
二、技术选型与核心依赖
1. 核心技术栈
- 可视化引擎:Cesium(Web 端 3D 地理信息可视化核心,支持地球、卫星、轨道等空间数据渲染)
- 前端框架:Vue.js(组件化开发,便于功能拆分和维护)
- 数据处理:satellite.js(解析 TLE 卫星轨道数据,计算卫星实时位置)
- 样式预处理:SCSS(高效编写结构化样式)
2. 关键依赖说明
- Cesium:提供地球渲染、实体管理、相机控制、时间轴等核心能力,是整个系统的基础
- satellite.js:专业的卫星轨道计算库,支持 TLE 数据解析、ECI 坐标系转大地坐标系等关键计算
- 自定义工具库:包含时间格式化、坐标转换、通信链路材质等工具函数,适配业务需求
三、核心原理与架构设计
1. 系统整体架构
- 数据层:卫星 TLE 数据(按轨道类型分文件存储)、雷达点位数据(含经纬度、类型等)
- 核心层:Cesium 引擎封装(地球初始化、实体管理、相机控制)、轨道计算(基于 satellite.js)、通信链路判断
- 交互层:轨道切换、鼠标事件、时间轴控制、信息弹窗
- 表现层:卫星 3D 模型、雷达模型、动态通信链路、信息面板
2. 关键技术原理
(1)卫星轨道计算原理
卫星轨道数据采用 TLE(两行轨道根数)格式存储,通过satellite.js的twoline2satrec方法解析 TLE 数据,得到卫星轨道参数。再通过propagate方法根据当前时间计算卫星的地心惯性坐标系(ECI)位置,最后通过eciToGeodetic方法转换为大地坐标系(经纬度 + 高度),实现卫星实时位置的精准计算。
(2)通信链路动态渲染
通信链路的显示状态由时间区间和通断状态控制:
- 预定义每条链路的有效时间区间和对应状态(正常、质量差、中断)
- 监听 Cesium 时钟
onTick事件,实时获取当前时间戳 - 遍历卫星 / 雷达实体,判断当前时间是否在链路有效区间内
- 对有效链路,通过
CallbackProperty实时更新链路端点坐标,结合自定义流动材质实现动态飞线效果
(3)多轨道切换与相机适配
不同轨道类型(高轨、近地轨)的卫星高度差异巨大,切换轨道时:
- 清空当前轨道的卫星、链路等实体数据
- 加载对应轨道的卫星 TLE 数据,重新计算轨道和位置
- 根据轨道高度自动调整相机视角距离(高轨 1.2 亿米,近地轨 2500 万米)
- 重新初始化通信链路计算逻辑
四、完整实现步骤
1. 项目依赖安装
# 安装核心依赖
npm install cesium satellite.js --save
2. 核心配置(Cesium 引入与 Token 设置)
在public/index.html中引入 Cesium 静态资源(或通过模块化引入):
<!-- 引入Cesium样式 -->
<link rel="stylesheet" href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" />
<!-- 引入Cesium核心库 -->
<script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script>
在 Vue 组件中配置 Cesium Ion Token(用于加载默认资源):
// 组件初始化时设置
Cesium.Ion.defaultAccessToken = '你的Cesium Ion Token';
3. 核心组件实现(带关键注释)
<template>
<div id="myCesium">
<!-- 头部标题 -->
<div class="header">
<p class="title">激光网路管理</p>
</div>
<!-- Cesium地球容器 -->
<div id="cesiumContainer" />
<!-- 加载遮罩 -->
<div id="loadingOverlay">
<h1>Loading...</h1>
</div>
<!-- 轨道切换标签 -->
<div class="tabContainer">
<ul class="tabs">
<li
v-for="(item, index) in tabs"
:key="index"
class="tab"
:class="tabIndex === index ? 'active' : ''"
@click="changeTab(index)"
>
{{ item }}
</li>
</ul>
</div>
</div>
</template>
<script>
import * as Cesium from 'cesium'
import { DateTimeFormatter, JulianDateToTimestamp, TimeFormatter, degreesFromCartesian } from '@/utils/tools.js'
import { twoline2satrec, propagate, gstime, eciToGeodetic, degreesLong, degreesLat } from 'satellite.js'
import LineFlowMaterialProperty from '@/utils/lineFlowMaterialProperty.js'
// 全局变量定义
let viewer // Cesium视图实例
const myMapToken = '你的天地图Token' // 天地图Token(需自行申请)
const time = new Date(2024, 1, 2, 12, 0, 0) // 基准时间
let max = time.getTime()
const nowTime = time.getTime()
const day = 1000 * 60 * 60 * 24 // 一天的毫秒数
let interval = 1000 * 10 // 卫星位置计算间隔(10秒)
let min = max - day // 时间轴起始时间(基准时间前一天)
let multiplier = 60 // 时间轴快进倍数
let showFlyObject = {} // 星间/星地通信链路存储
const showGroundObject = {} // 地面通信链路存储
let satelliteObj = {} // 卫星信息存储
// Cesium数据源定义
let satellites = new Cesium.CustomDataSource('satellite') // 卫星数据源
let polylines = new Cesium.CustomDataSource('statelliteLine') // 卫星轨道数据源
let radars = new Cesium.CustomDataSource('radar') // 雷达数据源
let connection = new Cesium.CustomDataSource('connection') // 通信链路数据源
// 3D模型映射
let satellitesModelMap = { 'LEO': '../models/satellite.glb' } // 卫星模型
let radarsModelMap = { 'radar': '../models/radar.glb', 'radar2': '../models/radar2.glb', 'ykCenter': '../models/ykCenter.glb' } // 雷达模型
// 通信链路状态映射
let polylineMaterialMap = {
'default': new Cesium.Color(1.0, 1.0, 1.0, 1),
'success': new Cesium.Color(0.0, 1.0, 0.0, 1),
'warning': new Cesium.Color(1.0, 1.0, 0.0, 1),
'error': new Cesium.Color(1.0, 0.0, 0.0, 1)
}
let connectionStateMap = {
'default': '无业务时线路通达',
'success': '有业务流、业务流正常',
'warning': '有业务流、质量差',
'error': '有业务流、线路断'
}
let radarPoints = [] // 雷达点位数据存储
export default {
name: 'LaserNetworkManager',
data () {
return {
tabs: ['高轨', '倾斜轨', '近地轨'],
tabIndex: 1, // 默认选中倾斜轨
handler: null // 鼠标事件处理器
}
},
mounted () {
this.initCesium() // 初始化Cesium
},
destroyed () {
this.destroyCesiumViewer() // 销毁Cesium实例,释放资源
},
methods: {
// 销毁Cesium视图
destroyCesiumViewer () {
if (viewer) {
viewer.destroy()
viewer = null
}
console.log('Cesium viewer destroyed')
},
// 初始化Cesium地球
initCesium () {
// 初始化Cesium视图
viewer = new Cesium.Viewer('cesiumContainer', {
homeButton: true, // 主页按钮
sceneModePicker: false, // 隐藏场景模式切换
baseLayerPicker: false, // 隐藏底图切换
animation: true, // 显示动画控件
infoBox: true, // 显示信息弹窗
selectionIndicator: true, // 显示选中指示器
geocoder: false, // 隐藏地名搜索
timeline: true, // 显示时间轴
fullscreenButton: true, // 显示全屏按钮
shouldAnimate: true, // 启用动画
navigationHelpButton: false // 隐藏帮助按钮
})
// 基础配置优化
viewer._cesiumWidget._creditContainer.style.display = 'none' // 隐藏Cesium Logo
viewer.scene.skyBox.show = true // 显示星空
viewer.scene.backgroundColor = Cesium.Color.BLACK // 背景色设为黑色
viewer.scene.sun.show = true // 显示太阳
viewer.scene.moon.show = true // 显示月亮
viewer.scene.skyAtmosphere.show = true // 显示大气层
viewer.scene.postProcessStages.fxaa.enabled = true // 开启抗锯齿
viewer.scene.globe.depthTestAgainstTerrain = false // 关闭地形深度检测
// 加载天地图底图(影像+路网注记)
viewer.imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({
url: `http://t0.tianditu.gov.cn/cia_w/wmts?tk=${myMapToken}`,
layer: 'cia',
style: 'default',
tileMatrixSetID: 'w',
format: 'tiles',
maximumLevel: 18
}))
// 设置初始相机视角
viewer.scene.camera.setView({
destination: new Cesium.Cartesian3.fromDegrees(116.4074, 0.9042, this.tabIndex === 0 ? 120000000 : 25000000),
orientation: {}
})
// 重写主页按钮行为
viewer.homeButton.viewModel.command.beforeExecute.addEventListener((e) => {
e.cancel = true
viewer.camera.flyTo({
destination: new Cesium.Cartesian3.fromDegrees(116.4074, 0.9042, this.tabIndex === 0 ? 120000000 : 25000000),
duration: 2.0
})
})
// 监听地球瓦片加载完成事件,隐藏加载遮罩
viewer.scene.globe.tileLoadProgressEvent.addEventListener(() => {
if (viewer.scene.globe.tilesLoaded) {
document.getElementById('loadingOverlay').style.display = 'none'
}
})
// 初始化时间轴
this.setTimeline()
// 加载核心数据
this.onViewerLoaded(viewer, this.tabIndex)
},
// 视图加载完成后初始化数据和事件
onViewerLoaded (Viewer, tabIndex) {
viewer = Viewer
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas)
// 加载卫星、雷达数据
this.loadSatelliteData(`satellite${tabIndex}`)
this.loadRadarData()
// 绑定鼠标事件
this.bindMouseEvents()
// 监听时钟 tick 事件,实时更新链路状态
viewer.clock.onTick.addEventListener(() => {
this.computeSatelliteToRadarRange() // 星地链路计算
this.computeSatellitesRange() // 星间链路计算
this.computeRadarsRange() // 地地链路计算
})
},
// 初始化时间轴(东八区时间,循环播放)
setTimeline () {
const startTime = Cesium.JulianDate.fromDate(new Date(min))
const stopTime = Cesium.JulianDate.fromDate(new Date(max))
viewer.clock.startTime = startTime.clone()
viewer.clock.stopTime = stopTime.clone()
viewer.clock.currentTime = stopTime.clone()
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP // 循环播放
viewer.clock.multiplier = multiplier // 快进倍数
viewer.timeline.zoomTo(startTime, stopTime) // 时间轴适配范围
// 自定义时间格式化
viewer.animation.viewModel.dateFormatter = DateTimeFormatter
viewer.animation.viewModel.timeFormatter = TimeFormatter
viewer.timeline.makeLabel = DateTimeFormatter
},
// 加载卫星数据(基于TLE)
loadSatelliteData (jsonName) {
document.getElementById('loadingOverlay').style.display = 'block'
fetch(`../json/${jsonName}.json`)
.then((res) => res.json())
.then((data) => {
satelliteObj = {} // 清空卫星数据
// 遍历卫星数据,解析TLE并计算位置
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
const element = data[key]
// 解析TLE数据
const satrec = twoline2satrec(element.tle[0], element.tle[1])
// 存储卫星基础信息
satelliteObj[key] = {
id: key,
name: key,
country: element.country,
role: element.role,
networkSystem: element.networkSystem,
type: element.type,
networkState: element.networkState,
times: [], // 时间戳数组
positions: [], // 位置数组(经纬度+高度)
toSatellitePolyLines: element.toSatellitePolyLines, // 星间链路配置
toRadarPolyLines: element.toRadarPolyLines // 星地链路配置
}
// 预计算卫星在时间区间内的所有位置
let longitude, latitude, height
for (let index = min; index <= nowTime; index += interval) {
// 计算卫星在当前时间的位置和速度
const positionAndVelocity = propagate(satrec, new Date(index))
const positionEci = positionAndVelocity.position // ECI坐标系位置
const gmst = gstime(new Date(index)) // 格林威治恒星时
const positionGd = eciToGeodetic(positionEci, gmst) // 转换为大地坐标系
// 转换为角度并存储
longitude = degreesLong(positionGd.longitude)
latitude = degreesLat(positionGd.latitude)
height = positionGd.height * 1000 // 高度转换为米
satelliteObj[key].times.push(index)
satelliteObj[key].positions.push([longitude, latitude, height])
}
}
}
// 计算卫星飞行轨迹并添加到视图
this.computeCircularFlight(satelliteObj)
document.getElementById('loadingOverlay').style.display = 'none'
}).catch(() => {
document.getElementById('loadingOverlay').style.display = 'none'
console.error('卫星数据加载失败')
})
},
// 加载雷达数据
loadRadarData () {
fetch('../json/radar.json')
.then((res) => res.json())
.then((data) => {
radars.entities.removeAll() // 清空原有雷达实体
radarPoints = data // 存储雷达数据
// 遍历创建雷达实体
data.forEach((item) => {
this.createRadar(item.id, item.lon, item.lat, item.type, item.toRadarPolyLines)
})
})
},
// 创建雷达实体
createRadar (id, lon, lat, type, toRadarPolyLines) {
radars.entities.add({
id: id,
model: {
uri: radarsModelMap[type], // 雷达3D模型
minimumPixelSize: 32, // 最小像素大小
maximumScale: 10000000 // 最大缩放比例
},
label: new Cesium.LabelGraphics({ // 雷达标签
text: id,
font: '12px sans-serif',
fillColor: Cesium.Color.RED,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 1,
pixelOffset: new Cesium.Cartesian2(0, 14)
}),
position: Cesium.Cartesian3.fromDegrees(lon, lat, 0), // 雷达位置(经纬度+高度)
toRadarPolyLines: toRadarPolyLines // 雷达间链路配置
})
viewer.dataSources.add(radars) // 添加雷达数据源到视图
},
// 计算卫星飞行轨迹并创建卫星实体
computeCircularFlight (obj, hasLine = false) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const element = obj[key]
const property = new Cesium.SampledPositionProperty() // 采样位置属性(支持时间插值)
const length = element.positions?.length
const positions = []
let p, t
// 填充卫星位置数据
for (let index = 0; index < length; index++) {
p = element.positions[index]
t = element.times[index]
// 添加位置采样点(时间+坐标)
property.addSample(
Cesium.JulianDate.fromDate(new Date(t)),
Cesium.Cartesian3.fromDegrees(p[0], p[1], p[2])
)
positions.push(...element.positions[index])
}
// 添加卫星实体到视图
satellites.entities.add({
id: key,
name: key,
model: {
uri: satellitesModelMap[element.role], // 卫星3D模型
minimumPixelSize: this.tabIndex === 0 ? 96 : 32, // 高轨卫星放大显示
maximumScale: 200000,
color: new Cesium.Color(1.0, 1.0, 1.0, 1.0)
},
// 卫星属性信息(用于弹窗展示)
country: element.country,
role: element.role,
networkSystem: element.networkSystem,
type: element.type,
networkState: element.networkState,
toSatellitePolyLines: element.toSatellitePolyLines,
toRadarPolyLines: element.toRadarPolyLines,
position: property, // 卫星位置(随时间变化)
orientation: new Cesium.VelocityOrientationProperty(property), // 基于速度的朝向
description: `
卫星名称:${key}<br/>
卫星标识:XXX <br/>
研制单位:XXX <br/>
模块组成:XXX <br/>
首次入轨时间:XXX <br/>
首次通信时间:XXX `
})
// 可选:显示卫星轨道线
if (hasLine) {
polylines.entities.add({
id: key,
polyline: {
width: 1,
material: Cesium.Color.BLUE.withAlpha(0.5),
positions: Cesium.Cartesian3.fromDegreesArrayHeights(positions)
},
description: `这是${key}的运行轨道`
})
}
}
}
// 添加数据源到视图
viewer.dataSources.add(satellites)
viewer.dataSources.add(polylines)
viewer.dataSources.add(connection)
},
// 绑定鼠标事件
bindMouseEvents () {
// 双击雷达聚焦
this.handler.setInputAction((movement) => {
const pickedObject = viewer.scene.pick(movement.position)
if (Cesium.defined(pickedObject)) {
const id = pickedObject.id.id
console.log(`左击${id}`)
// 显示可拖拽信息弹窗
this.showDraggableInfoBox(movement.position)
// 判断是否点击雷达
const isFocusRadar = radarPoints.find(item => item.id === id)
if (isFocusRadar) {
// 相机飞至雷达上方
const newCartesian = Cesium.Cartesian3.fromDegrees(
isFocusRadar.lon,
isFocusRadar.lat - 15, // 纬度减15度,便于观察
1000000 // 高度100万米
)
viewer.camera.flyTo({
destination: newCartesian,
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-30.0),
roll: 0.0
},
duration: 2.0
})
}
}
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
// 右键查看信息
this.handler.setInputAction((movement) => {
const pickedObject = viewer.scene.pick(movement.position)
if (Cesium.defined(pickedObject)) {
const id = pickedObject.id.id
console.log(`右击${id}`)
// 可扩展:右键菜单(显示详情、隐藏实体等)
}
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
},
// 显示可拖拽信息弹窗
showDraggableInfoBox (position) {
const infoBoxElement = document.querySelector('.cesium-infoBox')
if (!infoBoxElement) return
// 设置弹窗初始位置
infoBoxElement.style.top = position.y + 'px'
infoBoxElement.style.left = position.x + 10 + 'px'
infoBoxElement.style.position = 'absolute'
infoBoxElement.style.zIndex = '9999'
let isDragging = false
let initialX = 0
let initialY = 0
// 拖拽逻辑
infoBoxElement.addEventListener('mousedown', (event) => {
isDragging = true
initialX = event.clientX - infoBoxElement.offsetLeft
initialY = event.clientY - infoBoxElement.offsetTop
})
document.addEventListener('mousemove', (event) => {
if (isDragging) {
const offsetX = event.clientX - initialX
const offsetY = event.clientY - initialY
infoBoxElement.style.left = offsetX + 'px'
infoBoxElement.style.top = offsetY + 'px'
}
})
document.addEventListener('mouseup', () => {
isDragging = false
})
},
// 通信链路计算核心方法(通用)
computeRange (entities1, entities2, polyLinesProperty, resultObject) {
entities1.entities.values.forEach((entity1) => {
entities2.entities.values.forEach((entity2) => {
if (entity1 === entity2) return // 跳过自身
// 获取两个实体当前时间的位置
const pos1 = entity1.position?.getValue(viewer.clock.currentTime)
const pos2 = entity2.position?.getValue(viewer.clock.currentTime)
const currentTimestamp = JulianDateToTimestamp(viewer.clock.currentTime)
// 查找当前实体的链路配置
const linkConfig = entity1[polyLinesProperty]?.find(item => item.id === `${entity1.id} - ${entity2.id}`)
if (!linkConfig) return
// 判断当前时间是否在链路有效区间内
const isLinkActive = this.findBooleanByTimestamp(linkConfig.show, currentTimestamp)
const linkState = this.findStateByTimestamp(linkConfig.show, currentTimestamp)
if (pos1 && pos2 && isLinkActive) {
// 转换为经纬度高度
const po1 = degreesFromCartesian(pos1)
const po2 = degreesFromCartesian(pos2)
// 更新或创建链路实体
if (resultObject[`${entity1.id} - ${entity2.id}`]) {
resultObject[`${entity1.id} - ${entity2.id}`].show = true
resultObject[`${entity1.id} - ${entity2.id}`].state = linkState
resultObject[`${entity1.id} - ${entity2.id}`].po1 = po1
resultObject[`${entity2.id} - ${entity1.id}`].po2 = po2
} else {
resultObject[`${entity1.id} - ${entity2.id}`] = {
entity: null,
show: true,
state: linkState,
po1: po1,
po2: po2
}
}
} else if (resultObject[`${entity1.id} - ${entity2.id}`]) {
// 链路无效时隐藏
resultObject[`${entity1.id} - ${entity2.id}`].show = false
}
})
})
},
// 根据时间戳查找链路是否显示
findBooleanByTimestamp (showIntervals, timestamp) {
for (const interval of showIntervals || []) {
const [start, end] = interval.interval.split('/').map(Number)
if (timestamp >= start && timestamp < end) {
return interval.boolean
}
}
return false
},
// 根据时间戳查找链路状态
findStateByTimestamp (showIntervals, timestamp) {
for (const interval of showIntervals || []) {
const [start, end] = interval.interval.split('/').map(Number)
if (timestamp >= start && timestamp < end) {
return interval.state
}
}
return 'default'
},
// 星地链路计算
computeSatelliteToRadarRange () {
this.computeRange(satellites, radars, 'toRadarPolyLines', showFlyObject)
this.updateFlyLine()
},
// 星间链路计算
computeSatellitesRange () {
this.computeRange(satellites, satellites, 'toSatellitePolyLines', showFlyObject)
this.updateFlyLine()
},
// 地地链路计算
computeRadarsRange () {
this.computeRange(radars, radars, 'toRadarPolyLines', showGroundObject)
this.updateGroundLine()
},
// 更新星间/星地链路(飞线)
updateFlyLine () {
for (const key in showFlyObject) {
const link = showFlyObject[key]
if (!link.entity) {
link.entity = this.createFlyLine(key)
}
link.entity.show = link.show
// 更新链路材质颜色(根据状态)
link.entity.polyline.material.color = polylineMaterialMap[link.state]
}
},
// 创建飞线实体
createFlyLine (id) {
const linePositions = new Cesium.CallbackProperty(() => {
const link = showFlyObject[id]
return Cesium.Cartesian3.fromDegreesArrayHeights([
link.po1.longitude, link.po1.latitude, link.po1.height,
link.po2.longitude, link.po2.latitude, link.po2.height
])
}, false)
return connection.entities.add({
id: id,
polyline: {
positions: linePositions,
width: 2,
arcType: Cesium.ArcType.NONE,
// 自定义流动材质
material: new LineFlowMaterialProperty({
color: Cesium.Color.WHITE,
speed: 5,
percent: 0.5,
gradient: 0.5
})
},
description: `
链路名称:${id}<br/>
通断状态:${connectionStateMap[showFlyObject[id].state]}<br/>
链路类型:星间同轨 <br/>
链路距离:XXX km <br/>
相位差:XXX μrad(微弧度)<br/>
轨道高度:XXX km<br/>
双端终端ID:${id} <br/>
A/B状态:A <br/>
通信速率:XXX Mbps/Gbps`
})
},
// 更新地面链路
updateGroundLine () {
for (const key in showGroundObject) {
const link = showGroundObject[key]
if (!link.entity) {
link.entity = this.createGroundLine(key)
}
link.entity.show = link.show
}
},
// 创建地面链路实体
createGroundLine (id) {
return connection.entities.add({
id: id,
polyline: {
positions: new Cesium.CallbackProperty(() => {
const link = showGroundObject[id]
return Cesium.Cartesian3.fromDegreesArrayHeights([
link.po1.longitude, link.po1.latitude, link.po1.height,
link.po2.longitude, link.po2.latitude, link.po2.height
])
}, false),
width: 3,
material: new LineFlowMaterialProperty({
color: showGroundObject[id].state,
speed: 10,
percent: 0.5,
gradient: 0.5
}),
clampToGround: true // 贴地显示
},
description: `这是${id}通信线`
})
},
// 轨道切换
changeTab (index) {
this.tabIndex = index
// 清空原有数据
satellites.entities.removeAll()
polylines.entities.removeAll()
connection.entities.removeAll()
showFlyObject = {}
// 重新加载卫星数据
this.loadSatelliteData(`satellite${index}`)
// 调整相机视角
viewer.scene.camera.setView({
destination: new Cesium.Cartesian3.fromDegrees(116.4074, 0.9042, this.tabIndex === 0 ? 120000000 : 25000000),
orientation: {}
})
// 重新计算链路
this.computeSatelliteToRadarRange()
this.computeSatellitesRange()
this.computeRadarsRange()
}
}
}
</script>
<style scoped lang="scss">
#myCesium {
width: 100vw;
height: 100vh;
position: relative;
.header {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
.title {
font-size: 24px;
color: #fff;
font-weight: bold;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
}
#cesiumContainer {
width: 100%;
height: 100%;
}
#loadingOverlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
color: #fff;
font-size: 24px;
}
.tabContainer {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
.tabs {
display: flex;
gap: 20px;
list-style: none;
.tab {
padding: 8px 16px;
background: rgba(0, 0, 0, 0.6);
color: #fff;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.8);
}
&.active {
background: #1E90FF;
color: #fff;
}
}
}
}
}
</style>
4. 自定义流动材质(LineFlowMaterialProperty.js)
实现通信链路的流动效果,核心是通过 Shader 动态修改纹理坐标:
import * as Cesium from 'cesium';
class LineFlowMaterialProperty {
constructor (options) {
this._definitionChanged = new Cesium.Event()
this._color = undefined
this._speed = undefined
this._percent = undefined
this._gradient = undefined
this._positionOffset = undefined
this.color = options.color
this.speed = options.speed
this.percent = options.percent
this.gradient = options.gradient
this.positionOffset = options.positionOffset
}
get isConstant () {
return false
}
get definitionChanged () {
return this._definitionChanged
}
getType (time) {
return Cesium.Material.LineFlowMaterialType
}
getValue (time, result) {
if (!Cesium.defined(result)) {
result = {}
}
result.color = Cesium.Property.getValueOrDefault(this._color, time, Cesium.Color.RED, result.color)
result.speed = Cesium.Property.getValueOrDefault(this._speed, time, 5.0, result.speed)
result.percent = Cesium.Property.getValueOrDefault(this._percent, time, 0.1, result.percent)
result.gradient = Cesium.Property.getValueOrDefault(this._gradient, time, 0.01, result.gradient)
result.positionOffset = Cesium.Property.getValueOrDefault(this._positionOffset, time, 0.01, result.positionOffset)
return result
}
equals (other) {
return (this === other ||
(other instanceof LineFlowMaterialProperty &&
Cesium.Property.equals(this._color, other._color) &&
Cesium.Property.equals(this._speed, other._speed) &&
Cesium.Property.equals(this._percent, other._percent) &&
Cesium.Property.equals(this._gradient, other._gradient) &&
Cesium.Property.equals(this._positionOffset, other._positionOffset))
)
}
}
Object.defineProperties(LineFlowMaterialProperty.prototype, {
color: Cesium.createPropertyDescriptor('color'),
speed: Cesium.createPropertyDescriptor('speed'),
percent: Cesium.createPropertyDescriptor('percent'),
gradient: Cesium.createPropertyDescriptor('gradient'),
positionOffset: Cesium.createPropertyDescriptor('positionOffset')
})
Cesium.LineFlowMaterialProperty = LineFlowMaterialProperty
Cesium.Material.LineFlowMaterialProperty = 'LineFlowMaterialProperty'
Cesium.Material.LineFlowMaterialType = 'LineFlowMaterialType'
Cesium.Material.LineFlowMaterialSource =
`
uniform vec4 color;
uniform float speed;
uniform float percent;
uniform float gradient;
uniform float positionOffset;
czm_material czm_getMaterial(czm_materialInput materialInput){
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float t =fract(czm_frameNumber * speed/ 1000.0);
t *= (1.0 + percent);
float position = mod(st.s + positionOffset, 1.0);
float alpha = smoothstep(t- percent, t, st.s) * step(-t, -st.s);
alpha += gradient;
material.diffuse = color.rgb;
material.alpha = alpha;
return material;
}
`
Cesium.Material._materialCache.addMaterial(Cesium.Material.LineFlowMaterialType, {
fabric: {
type: Cesium.Material.LineFlowMaterialType,
uniforms: {
color: new Cesium.Color(0.0, 0.0, 0.0, 0),
speed: 10.0,
percent: 0.1,
gradient: 0.1,
positionOffset: 50
},
source: Cesium.Material.LineFlowMaterialSource
},
translucent: function (material) {
return true
}
})
5. 数据格式说明
(1)卫星 TLE 数据(satellite1.json)
{
"卫星1": {
"tle": [
"1 25544U 98067A 24035.58333333 .00016717 00000+0 10270-3 0 9995",
"2 25544 51.6400 45.4650 0006703 30.1234 329.8766 15.50000000 32425"
],
"country": "中国",
"role": "LEO",
"networkSystem": "激光通信网",
"type": "倾斜轨",
"networkState": "正常",
"toSatellitePolyLines": [
{
"id": "卫星1 - 卫星2",
"show": [
{ "interval": "1704105600000/1704192000000", "boolean": true, "state": "success" },
{ "interval": "1704192000000/1704278400000", "boolean": false, "state": "default" }
]
}
],
"toRadarPolyLines": [
{
"id": "卫星1 - 雷达A",
"show": [
{ "interval": "1704105600000/1704192000000", "boolean": true, "state": "success" }
]
}
]
}
}
(2)雷达数据(radar.json)
[
{
"id": "雷达A",
"lon": 116.4074,
"lat": 39.9042,
"type": "radar",
"toRadarPolyLines": [
{
"id": "雷达A - 雷达B",
"show": [
{ "interval": "1704105600000/1704278400000", "boolean": true, "state": "success" }
]
}
]
}
]