import * as Cesium from 'cesium'; import { type Viewer } from 'cesium'; import { eciToEcf, gstime, propagate, twoline2satrec } from 'satellite.js'; /** * 未来2小时内每30秒的位置 */ export async function demo_02_Track(viewer: Viewer) { const tle = `STARLINK-11371 [DTC] 1 62879U 25024A 25062.93300820 .00003305 00000+0 21841-4 0 9995 2 62879 42.9977 257.3937 0001725 269.2925 90.7748 15.77864921 5143`; // 解析TLE数据 const lines = tle.split('\n') as [string, string, string]; const satelliteName = lines[0]?.trim(); const satrec = twoline2satrec(lines[1], lines[2]); // 创建卫星实体 const satelliteEntity = viewer.entities.add({ id: 'STARLINK-11371', label: { fillColor: Cesium.Color.WHITE, font: '14pt sans-serif', outlineColor: Cesium.Color.BLACK, outlineWidth: 2, pixelOffset: new Cesium.Cartesian2(0, -10), style: Cesium.LabelStyle.FILL_AND_OUTLINE, text: satelliteName, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, }, name: 'STARLINK-11371', // 卫星轨迹 path: { material: new Cesium.PolylineGlowMaterialProperty({ color: Cesium.Color.BLUE, glowPower: 0.2, }), resolution: 1, width: 2, }, // 使用简单点图形表示卫星 point: { color: Cesium.Color.YELLOW, outlineColor: Cesium.Color.WHITE, outlineWidth: 2, pixelSize: 10, }, }); // 计算并绘制卫星轨迹 const totalSeconds = 60 * 60 * 2; // 2小时的轨迹 const timeStepInSeconds = 30; // 每30秒一个点 const startTime = Cesium.JulianDate.fromDate(new Date()); const endTime = Cesium.JulianDate.addSeconds(startTime, totalSeconds, new Cesium.JulianDate()); // 设置时钟范围 viewer.clock.startTime = startTime.clone(); viewer.clock.stopTime = endTime.clone(); viewer.clock.currentTime = startTime.clone(); viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; viewer.clock.multiplier = 30; // 30倍速播放 // 设置时间轴范围 viewer.timeline.zoomTo(startTime, endTime); // 用于存储卫星位置的样本集合 const positionProperty = new Cesium.SampledPositionProperty(); // 用于存储轨道点的数组(用于显示完整轨道) const orbitPositions: Cesium.Cartesian3[] = []; // 保存卫星高度数据,用于覆盖范围计算 // const satelliteAltitude = 550_000; // 默认高度为550公里 // 计算轨道上的点 for (let i = 0; i <= totalSeconds; i += timeStepInSeconds) { const time = Cesium.JulianDate.addSeconds(startTime, i, new Cesium.JulianDate()); const jsDate = Cesium.JulianDate.toDate(time); // 计算卫星位置 const positionAndVelocity = propagate(satrec, jsDate); if (typeof positionAndVelocity.position === 'boolean') { console.error('Error calculating satellite position'); } else { const gmst = gstime(jsDate); const position = eciToEcf(positionAndVelocity.position, gmst); // 转换为Cesium坐标(单位:米) const cesiumPosition = new Cesium.Cartesian3(position.x * 1000, position.y * 1000, position.z * 1000); // 计算卫星高度 // const cartographic = Cesium.Cartographic.fromCartesian(cesiumPosition); // satelliteAltitude = cartographic.height; // 保存位置用于绘制完整轨道 orbitPositions.push(cesiumPosition); // 添加位置样本 positionProperty.addSample(time, cesiumPosition); } } // 添加完整轨道线 viewer.entities.add({ id: 'STARLINK-11371-orbit', name: 'STARLINK-11371 Full Orbit', polyline: { clampToGround: false, material: new Cesium.PolylineDashMaterialProperty({ color: Cesium.Color.CYAN, dashLength: 8, }), positions: orbitPositions, width: 1, }, }); // 设置卫星的位置 satelliteEntity.position = positionProperty; satelliteEntity.orientation = new Cesium.VelocityOrientationProperty(positionProperty); // 添加卫星覆盖范围 // Starlink 卫星通常覆盖范围以锥形方式向地面投射 // 创建一个椭球体来表示卫星的覆盖范围 const coverageAngle = 45; // 覆盖角度(度) // satelliteAltitude * Math.tan(Cesium.Math.toRadians(coverageAngle)); // 创建覆盖范围实体 viewer.entities.add({ ellipsoid: { material: Cesium.Color.BLUE.withAlpha(0.2), outline: true, outlineColor: Cesium.Color.BLUE.withAlpha(0.8), radii: new Cesium.CallbackProperty(() => { const position = satelliteEntity.position?.getValue(viewer.clock.currentTime); if (!position) return new Cesium.Cartesian3(10_000, 10_000, 10_000); // 获取当前卫星高度 const cartographic = Cesium.Cartographic.fromCartesian(position); const height = cartographic.height; // 计算覆盖范围半径 (基于锥角) const coverageRadius = height * Math.tan(Cesium.Math.toRadians(coverageAngle)); // 椭球体形状: xy平面为圆形基底,z轴方向为高度 return new Cesium.Cartesian3(coverageRadius, coverageRadius, height / 2); }, false), slicePartitions: 24, stackPartitions: 16, }, id: 'STARLINK-11371-coverage', name: 'STARLINK-11371 Coverage', orientation: new Cesium.CallbackProperty(() => { // 确保椭球体始终指向地球中心 const position = satelliteEntity.position?.getValue(viewer.clock.currentTime); if (!position) return Cesium.Transforms.eastNorthUpToFixedFrame(new Cesium.Cartesian3()); // 计算从卫星到地球中心的方向 Cesium.Cartesian3.normalize(Cesium.Cartesian3.negate(position, new Cesium.Cartesian3()), new Cesium.Cartesian3()); return Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(0, Math.PI, 0)); }, false), position: positionProperty, }); // 添加地面覆盖范围投影 viewer.entities.add({ ellipse: { granularity: Cesium.Math.toRadians(1), height: 0, material: Cesium.Color.BLUE.withAlpha(0.2), outline: true, outlineColor: Cesium.Color.BLUE.withAlpha(0.8), outlineWidth: 2, semiMajorAxis: new Cesium.CallbackProperty(() => { const satPosition = satelliteEntity.position?.getValue(viewer.clock.currentTime); if (!satPosition) return 10_000; // 计算卫星高度 const cartographic = Cesium.Cartographic.fromCartesian(satPosition); // 根据高度和覆盖角度计算地面覆盖半径 return cartographic.height * Math.tan(Cesium.Math.toRadians(coverageAngle)); }, false), semiMinorAxis: new Cesium.CallbackProperty(() => { const satPosition = satelliteEntity.position?.getValue(viewer.clock.currentTime); if (!satPosition) return 10_000; // 计算卫星高度 const cartographic = Cesium.Cartographic.fromCartesian(satPosition); // 根据高度和覆盖角度计算地面覆盖半径 return cartographic.height * Math.tan(Cesium.Math.toRadians(coverageAngle)); }, false), }, id: 'STARLINK-11371-ground-coverage', name: 'STARLINK-11371 Ground Coverage', position: new Cesium.CallbackPositionProperty(() => { const satPosition = satelliteEntity.position?.getValue(viewer.clock.currentTime); if (!satPosition) return new Cesium.Cartesian3(); // 计算卫星在地表的投影点 const cartographic = Cesium.Cartographic.fromCartesian(satPosition); return Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0); }, false), }); // - https://community.cesium.com/t/entity-viewfrom-property-example/2299/3 // - https://github.com/CesiumGS/cesium/issues/8900#issuecomment-638114149 // - which is normal from a bird's eye view. But after setting it as the trackedEntity, it behaves abnormally. // 设置相机自动跟踪卫星 viewer.trackedEntity = satelliteEntity; // 开始动画 viewer.clock.shouldAnimate = true; }