import * as Cesium from 'cesium'; import { eciToEcf, gstime, propagate, type SatRec, twoline2satrec } from 'satellite.js'; import type { GroundStationOptions, SatelliteOptions } from './h-cesium-viewer-class.types'; // 2小时 import { VIEWER_OPTIONS_FN } from './helper/_VIEWER_OPTIONS'; import { configureCesium } from './helper/configureCesium'; import { configureTimeLine } from './helper/configureTimeLine'; const 默认轨道时长秒 = 2 * 60 * 60; export { type GroundStationOptions, type SatelliteOptions } from './h-cesium-viewer-class.types'; Cesium.Ion.defaultAccessToken = import.meta.env.VITE_CESIUM_ION_TOKEN; // 用了离线地图的情况是不需要的。 Object.assign(globalThis, { Cesium }); configureCesium(); export class HCesiumViewerCls { viewer: Cesium.Viewer | null = null; // 用于存储当前地面站实体的 Map currentStationEntities: Map = new Map(); // 用于存储当前卫星实体的 Map (包括轨道实体) currentSatelliteEntities: Map< string, { coverageEntity?: Cesium.Entity; entity: Cesium.Entity; orbitEntity?: Cesium.Entity; } > = new Map(); /** * 初始化 Cesium Viewer * @param container - 用于承载 Cesium Viewer 的 DOM 元素或其 ID */ initCesiumViewer(container: ConstructorParameters[0]) { this.viewer = new Cesium.Viewer(container, VIEWER_OPTIONS_FN()); configureTimeLine(this.viewer); this.viewer.scene.debugShowFramesPerSecond = true; // 初始化时清空可能存在的旧实体引用 this.currentStationEntities.clear(); this.currentSatelliteEntities.clear(); } // region 地面站点相关方法 /** * 向视图中添加地面站实体 * @param options - 地面站的选项参数 * @returns 添加的实体对象,如果添加失败则返回 null。 */ addGroundStation(options: GroundStationOptions): Cesium.Entity | null { if (!this.viewer) { console.error('视图未初始化。无法添加地面站。'); return null; } // 检查是否已存在相同 ID 的实体 if (this.currentStationEntities.has(options.id)) { console.warn(`ID 为 "${options.id}" 的地面站实体已存在,跳过添加。`); return this.currentStationEntities.get(options.id) || null; } // 解构赋值获取站点信息 const { height = 0, id, latitude, longitude, name, pixelSize = 10 } = options; const position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height); const groundStationEntity = new Cesium.Entity({ // 使用传入的 id 作为实体的唯一标识符 id: id, label: { font: '14pt sans-serif', outlineWidth: 2, pixelOffset: new Cesium.Cartesian2(0, -12), // 标签略微偏移到点的上方 style: Cesium.LabelStyle.FILL_AND_OUTLINE, text: name, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, }, name: name, point: { color: Cesium.Color.fromRandom(), // 随机颜色 outlineColor: Cesium.Color.WHITE, outlineWidth: 2, pixelSize, }, position: position, }); const addedEntity = this.viewer.entities.add(groundStationEntity); // 添加成功后,将其存入 Map this.currentStationEntities.set(id, addedEntity); return addedEntity; } /** * 从视图中移除指定的地面站实体 (通过 ID) * @param entityId - 要移除的地面站实体的 ID * @returns 如果成功移除则返回 true,否则返回 false */ removeGroundStationById(entityId: string): boolean { if (!this.viewer) { console.error('视图未初始化。无法移除地面站。'); return false; } const entityToRemove = this.currentStationEntities.get(entityId); if (entityToRemove) { const removed = this.viewer.entities.remove(entityToRemove); if (removed) { // 移除成功后,从 Map 中删除 this.currentStationEntities.delete(entityId); return true; } else { console.warn(`尝试从 Cesium 移除 ID 为 "${entityId}" 的实体失败。`); return false; // Cesium 移除失败 } } else { // console.warn(`未在 Map 中找到 ID 为 "${entityId}" 的地面站实体,无法移除。`); // 可能在 clearAll 时触发,不一定是警告 return false; // Map 中未找到 } } clearAllGroundStations() { if (!this.viewer) return; for (const entity of this.currentStationEntities.values()) { this.viewer?.entities.remove(entity); } 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, orbitDurationSeconds = 默认轨道时长秒, 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 randomBaseColor = Cesium.Color.fromRandom(); // 创建卫星实体 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: randomBaseColor, // 使用随机基色 outlineColor: Cesium.Color.WHITE, outlineWidth: 1, }, // 动态轨迹路径 path: { resolution: 1, material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.15, color: randomBaseColor, // 使用随机基色 }), width: 2, leadTime: orbitDurationSeconds / 2, // 显示未来一半时间的轨迹 trailTime: orbitDurationSeconds / 2, // 显示过去一半时间的轨迹 }, }); // --- 计算轨道 --- const startTime = this.viewer.clock.currentTime; // 使用当前 viewer 的时间作为起点 const positionProperty = new Cesium.SampledPositionProperty(); const orbitPositions: Cesium.Cartesian3[] = []; // 用于存储完整轨道点 for (let i = 0; i <= orbitDurationSeconds; 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); // --- 添加卫星地面覆盖范围 --- // 使用 CallbackProperty 动态计算星下点位置 const subsatellitePosition = new Cesium.CallbackPositionProperty( (time, result) => { // 从 satelliteEntity 获取当前时间的精确位置 const satelliteCartesian = positionProperty.getValue(time, result); if (!satelliteCartesian) { return; // 如果位置无效,则不返回任何内容 } // 转换为地理坐标(包含高度) const satelliteCartographic = Cesium.Cartographic.fromCartesian(satelliteCartesian); if (!satelliteCartographic) { return; // 如果转换失败,则不返回任何内容 } // 创建星下点地理坐标(高度设为0) const subsatelliteCartographic = new Cesium.Cartographic( satelliteCartographic.longitude, satelliteCartographic.latitude, 0, ); // 转换回笛卡尔坐标 return Cesium.Cartographic.toCartesian(subsatelliteCartographic, Cesium.Ellipsoid.WGS84, result); }, false, Cesium.ReferenceFrame.FIXED, ); // isConstant: false, referenceFrame: FIXED // 使用 CallbackProperty 动态计算覆盖半径 (基于高度的简单估算) const coverageRadius = new Cesium.CallbackProperty((time) => { const satelliteCartesian = positionProperty.getValue(time); if (!satelliteCartesian) { return 100_000; // 默认半径 100km } const satelliteCartographic = Cesium.Cartographic.fromCartesian(satelliteCartesian); if (!satelliteCartographic) { return 100_000; } const altitude = satelliteCartographic.height; // 简化的估算:半径约为高度的 0.8 倍,最小 50km // 实际应用中应基于卫星的视场角 (FOV) 或波束宽度计算 const calculatedRadius = altitude * 0.8; return Math.max(calculatedRadius, 50_000); // 最小半径 50km }, false); // isConstant 设置为 false const coverageEntity = this.viewer.entities.add({ id: `${id}-coverage`, name: `${name} 覆盖范围`, position: subsatellitePosition, ellipse: { semiMajorAxis: coverageRadius, semiMinorAxis: coverageRadius, // 假设圆形覆盖 material: randomBaseColor.withAlpha(0.2 + 0.3), // 基于随机基色的半透明填充 heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, // 贴合地形 outline: true, outlineColor: randomBaseColor.withAlpha(0.8), // 基于随机基色的较深半透明轮廓 outlineWidth: 2, // granularity: Cesium.Math.toRadians(1), // 控制椭圆边缘平滑度 }, }); // --- 覆盖范围结束 --- 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: randomBaseColor.withAlpha(0.5), // 基于随机基色的半透明虚线 dashLength: 16, }), clampToGround: false, // 轨道不贴地 }, }); } // 存储实体引用 this.currentSatelliteEntities.set(id, { entity: satelliteEntity, orbitEntity, coverageEntity }); 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; let removedCoverage = 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}" 的实体失败。`); } } // 移除覆盖范围 if (satelliteData.coverageEntity) { removedCoverage = this.viewer.entities.remove(satelliteData.coverageEntity); if (!removedCoverage) { console.warn(`尝试从 Cesium 移除卫星覆盖范围 ID 为 "${satelliteData.coverageEntity.id}" 的实体失败。`); } } // 如果主体和轨道(如果存在)都移除成功或不存在,则从 Map 中删除 // 如果主体、轨道(如果存在)和覆盖范围(如果存在)都移除成功或不存在,则从 Map 中删除 if (removedMain && removedOrbit && removedCoverage) { 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(); // 确保清空 } }