diff --git a/src/components/h-cesium-viewer/h-cesium-viewer-class.ts b/src/components/h-cesium-viewer/h-cesium-viewer-class.ts index 2b38d38..6f39890 100644 --- a/src/components/h-cesium-viewer/h-cesium-viewer-class.ts +++ b/src/components/h-cesium-viewer/h-cesium-viewer-class.ts @@ -1,4 +1,5 @@ import * as Cesium from 'cesium'; +import { eciToEcf, gstime, propagate, type SatRec, twoline2satrec } from 'satellite.js'; import { VIEWER_OPTIONS_FN } from './VIEWER_OPTIONS'; @@ -11,10 +12,24 @@ export interface GroundStationOptions { pixelSize?: number; // 点的可选像素大小 } +// 卫星选项接口 +export interface SatelliteOptions { + id: string; // 卫星的唯一标识符 + orbitDurationHours?: number; // 轨道显示时长(小时),默认为 2 + showOrbit?: boolean; // 是否显示完整轨道线,默认为 true + timeStepSeconds?: number; // 轨道计算步长(秒),默认为 30 + tle: string; // 包含卫星名称和两行 TLE 数据的字符串,格式如下: + // NAME + // TLE1 + // TLE2 +} + export class HCesiumViewerCls { private viewer: Cesium.Viewer | null = null; // 用于存储当前地面站实体的 Map currentStationEntities: Map = new Map(); + // 用于存储当前卫星实体的 Map (包括轨道实体) + currentSatelliteEntities: Map = new Map(); /** * 初始化 Cesium Viewer @@ -22,10 +37,14 @@ export class HCesiumViewerCls { */ initCesiumViewer(container: ConstructorParameters[0]) { this.viewer = new Cesium.Viewer(container, VIEWER_OPTIONS_FN()); + // 初始化时清空可能存在的旧实体引用 this.currentStationEntities.clear(); + this.currentSatelliteEntities.clear(); } + // region 地面站点相关方法 + /** * 向视图中添加地面站实体 * @param options - 地面站的选项参数 @@ -108,13 +127,220 @@ export class HCesiumViewerCls { } this.currentStationEntities.clear(); } + // endregion 地面站点相关方法 + + // region 卫星相关方法 + + /** + * 向视图中添加卫星实体及其轨道 + * @param options - 卫星的选项参数 + * @returns 添加的卫星实体对象,如果添加失败则返回 null。 + */ + addSatellite(options: SatelliteOptions): Cesium.Entity | null { + if (!this.viewer) { + console.error('视图未初始化。无法添加卫星。'); + return null; + } + + // 检查是否已存在相同 ID 的实体 + if (this.currentSatelliteEntities.has(options.id)) { + console.warn(`ID 为 "${options.id}" 的卫星实体已存在,跳过添加。`); + return this.currentSatelliteEntities.get(options.id)?.entity || null; + } + + const { + id, + tle, + orbitDurationHours = 2, // 默认轨道时长 2 小时 + timeStepSeconds = 30, // 默认步长 30 秒 + showOrbit = true, // 默认显示轨道 + } = options; + + // --- 从 tle 字符串解析 name, tle1, tle2 --- + const tleLines = tle.trim().split('\n'); + if (tleLines.length < 3) { + console.error(`无效的 TLE 格式 (ID: ${id}): TLE 字符串 "${tle}" 至少需要三行`); + return null; + } + const name = tleLines[0].trim(); + const tle1 = tleLines[1].trim(); + const tle2 = tleLines[2].trim(); + // --- 解析结束 --- + + let satrec: SatRec; + try { + // 解析 TLE 数据 + satrec = twoline2satrec(tle1, tle2); + } catch (error) { + console.error(`解析 TLE 失败 (ID: ${id}):`, error); + return null; + } + + // 创建卫星实体 + const satelliteEntity = this.viewer.entities.add({ + id: id, + name: name, + label: { + text: name, + font: '14pt sans-serif', + style: Cesium.LabelStyle.FILL_AND_OUTLINE, + outlineWidth: 2, + pixelOffset: new Cesium.Cartesian2(0, -10), // 标签偏移 + verticalOrigin: Cesium.VerticalOrigin.BOTTOM, + fillColor: Cesium.Color.WHITE, + outlineColor: Cesium.Color.BLACK, + }, + // 使用点表示卫星 + point: { + pixelSize: 8, + color: Cesium.Color.YELLOW, + outlineColor: Cesium.Color.WHITE, + outlineWidth: 1, + }, + // 动态轨迹路径 + path: { + resolution: 1, + material: new Cesium.PolylineGlowMaterialProperty({ + glowPower: 0.15, + color: Cesium.Color.BLUE, + }), + width: 2, + leadTime: (orbitDurationHours * 3600) / 2, // 显示未来一半时间的轨迹 + trailTime: (orbitDurationHours * 3600) / 2, // 显示过去一半时间的轨迹 + }, + }); + + // --- 计算轨道 --- + const totalSeconds = orbitDurationHours * 60 * 60; + const startTime = this.viewer.clock.currentTime; // 使用当前 viewer 的时间作为起点 + const positionProperty = new Cesium.SampledPositionProperty(); + const orbitPositions: Cesium.Cartesian3[] = []; // 用于存储完整轨道点 + + for (let i = 0; i <= totalSeconds; i += timeStepSeconds) { + const time = Cesium.JulianDate.addSeconds(startTime, i, new Cesium.JulianDate()); + const jsDate = Cesium.JulianDate.toDate(time); + + try { + const positionAndVelocity = propagate(satrec, jsDate); + if (typeof positionAndVelocity.position === 'boolean') { + // 如果 propagate 返回布尔值,说明计算出错或卫星已衰减 + console.warn(`卫星 ${id} 在时间 ${jsDate} 位置计算失败或已衰减。`); + continue; // 跳过这个时间点 + } + + const gmst = gstime(jsDate); + const positionEcf = eciToEcf(positionAndVelocity.position, gmst); + + // 转换为 Cesium 坐标(单位:米) + const cesiumPosition = new Cesium.Cartesian3(positionEcf.x * 1000, positionEcf.y * 1000, positionEcf.z * 1000); + + // 添加位置样本 + positionProperty.addSample(time, cesiumPosition); + if (showOrbit) { + orbitPositions.push(cesiumPosition); + } + } catch (error) { + console.error(`计算卫星 ${id} 在时间 ${jsDate} 的位置时出错:`, error); + // 可以在这里决定是跳过还是中断循环 + continue; + } + } + + // 设置卫星的位置和方向 + satelliteEntity.position = positionProperty; + satelliteEntity.orientation = new Cesium.VelocityOrientationProperty(positionProperty); + + let orbitEntity: Cesium.Entity | undefined; + // 添加完整轨道线(如果需要) + if (showOrbit && orbitPositions.length > 1) { + orbitEntity = this.viewer.entities.add({ + id: `${id}-orbit`, // 轨道实体 ID + name: `${name} 轨道`, // 使用解析出的 name + polyline: { + positions: orbitPositions, + width: 1, + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.CYAN.withAlpha(0.5), // 半透明青色虚线 + dashLength: 16, + }), + clampToGround: false, // 轨道不贴地 + }, + }); + } + + // 存储实体引用 + this.currentSatelliteEntities.set(id, { entity: satelliteEntity, orbitEntity }); + + return satelliteEntity; + } + + /** + * 从视图中移除指定的卫星实体及其轨道 (通过 ID) + * @param entityId - 要移除的卫星实体的 ID + * @returns 如果成功移除则返回 true,否则返回 false + */ + removeSatelliteById(entityId: string): boolean { + if (!this.viewer) { + console.error('视图未初始化。无法移除卫星。'); + return false; + } + const satelliteData = this.currentSatelliteEntities.get(entityId); + if (satelliteData) { + let removedMain = true; + let removedOrbit = true; + + // 移除卫星主体 + if (satelliteData.entity) { + removedMain = this.viewer.entities.remove(satelliteData.entity); + if (!removedMain) { + console.warn(`尝试从 Cesium 移除卫星主体 ID 为 "${entityId}" 的实体失败。`); + } + } + + // 移除轨道线 + if (satelliteData.orbitEntity) { + removedOrbit = this.viewer.entities.remove(satelliteData.orbitEntity); + if (!removedOrbit) { + console.warn(`尝试从 Cesium 移除卫星轨道 ID 为 "${satelliteData.orbitEntity.id}" 的实体失败。`); + } + } + + // 如果主体和轨道(如果存在)都移除成功或不存在,则从 Map 中删除 + if (removedMain && removedOrbit) { + this.currentSatelliteEntities.delete(entityId); + return true; + } else { + // 如果有任何一个移除失败,保留在 Map 中可能有助于调试,但返回 false + return false; + } + } else { + // console.warn(`未在 Map 中找到 ID 为 "${entityId}" 的卫星实体,无法移除。`); // 可能在 clearAll 时触发 + return false; // Map 中未找到 + } + } + + /** 清除所有卫星实体和轨道 */ + clearAllSatellites() { + if (!this.viewer) return; + // 迭代 Map 的 ID 进行移除,避免在迭代 Map 值时修改 Map + const idsToRemove = [...this.currentSatelliteEntities.keys()]; + for (const id of idsToRemove) { + this.removeSatelliteById(id); // 使用封装好的移除方法 + } + // 确保清空 Map,即使移除过程中有失败 + this.currentSatelliteEntities.clear(); + } + + // endregion 卫星相关方法 destroy() { if (this.viewer) { this.clearAllGroundStations(); + this.clearAllSatellites(); this.viewer.destroy(); this.viewer = null; } this.currentStationEntities.clear(); // 确保清空 + this.currentSatelliteEntities.clear(); // 确保清空 } } diff --git a/src/components/h-cesium-viewer/index.vue b/src/components/h-cesium-viewer/index.vue index b37aa45..3b97fd1 100644 --- a/src/components/h-cesium-viewer/index.vue +++ b/src/components/h-cesium-viewer/index.vue @@ -1,13 +1,15 @@