diff --git a/src/pages/cesium/cesium-helper/00.cesium-init.ts b/src/pages/cesium/cesium-helper/00.cesium-init.ts index 1448dc9..4abd6bb 100644 --- a/src/pages/cesium/cesium-helper/00.cesium-init.ts +++ b/src/pages/cesium/cesium-helper/00.cesium-init.ts @@ -1,29 +1,23 @@ +// TODO: 干掉 api.cesium.com +// XXX: ion-sdk ??? + import * as Cesium from 'cesium'; import 'cesium/Build/Cesium/Widgets/widgets.css'; -// Cesium.Ion.defaultAccessToken = -// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiYjZmMWM4Ny01YzQ4LTQ3MzUtYTI5Mi1hNTgyNjdhMmFiMmMiLCJpZCI6NjIwMjgsImlhdCI6MTYyNjY3MTMxNX0.5SelYUyzXWRoMyjjFvmFIAoPtWlJPQMjsVl2e_jQe-c'; +Object.assign(window, { Cesium }); -export function cesium_init() { - // 复写原型方法 用于timeline组件日期格式化; - // @ts-expect-error node_modules/@cesium/widgets/Source/Timeline/Timeline.js - Cesium.Timeline.prototype.makeLabel = function (time) { - const minutes = 0 - new Date().getTimezoneOffset(); - const dataZone8 = Cesium.JulianDate.addMinutes(time, minutes, new Cesium.JulianDate()); - return Cesium.JulianDate.toIso8601(dataZone8).slice(0, 19); - }; +_configureCesium(); - // cesium-viewer-bottom - - const viewer = new Cesium.Viewer('cesiumContainer', { - animation: true, // 是否创建动画小部件 - // globe: false, // 地球 - baseLayerPicker: true, +export function cesium_init(container: Element) { + const viewer = new Cesium.Viewer(container, { + animation: true, // .cesium-viewer-animationContainer https://cesium.com/learn/ion-sdk/ref-doc/Animation.html + baseLayerPicker: !true, fullscreenButton: !true, // 全屏按钮 - geocoder: true, // = IonGeocodeProviderType.DEFAULT] - 在使用Geocoder小部件进行搜索时使用的地理编码服务或服务。如果设置为false,则不会创建Geocoder小部件。 + geocoder: !true, // = IonGeocodeProviderType.DEFAULT] - 在使用Geocoder小部件进行搜索时使用的地理编码服务或服务。如果设置为false,则不会创建Geocoder小部件。 + // globe: false, // 地球 homeButton: true, // Home按钮 - infoBox: true, // InfoBox小部件。 - navigationHelpButton: !true, // 是否显示导航帮助按钮 + infoBox: false, // InfoBox小部件。 + navigationHelpButton: false, // 是否显示导航帮助按钮 orderIndependentTranslucency: false, // 顺序无关透明度 projectionPicker: !true, // 投影选择器 requestRenderMode: !true, // 如果为真,渲染帧将仅在场景内部发生变化时需要时发生。启用此功能可以减少应用程序的CPU/GPU使用率,并在移动设备上节省更多电量,但在此模式下需要使用{@link Scene#requestRender}显式渲染新帧。在API的其他部分对场景进行更改后,在许多情况下都需要这样做。请参阅{@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|使用显式渲染提高性能}。 @@ -33,51 +27,87 @@ export function cesium_init() { /* animationContainer: !true, */ /* timelineContainer: true, */ /* bottomContainer: document.createElement('p'), // The DOM element or ID that will contain the bottomContainer. If not specified, the bottomContainer is added to the widget itself. */ - shouldAnimate: true, + shouldAnimate: !true, showRenderLoopErrors: true, // 如果为真,当发生渲染循环错误时,此小部件将自动向用户显示包含错误的HTML面板。 timeline: true, // If set to false, the Timeline widget will not be created. }); - /* if ($__DEV__) */ viewer.scene.debugShowFramesPerSecond = true; - // 时间格式化 - const minutes = 0 - new Date().getTimezoneOffset(); // 0 - (-480); - viewer.animation.viewModel.timeFormatter = function (date, _viewModel) { - const dataZone8 = Cesium.JulianDate.addMinutes(date, minutes, new Cesium.JulianDate()); - return Cesium.JulianDate.toIso8601(dataZone8).slice(11, 19); - }; - viewer.animation.viewModel.dateFormatter = function (date, _viewModel) { - const dataZone8 = Cesium.JulianDate.addMinutes(date, minutes, new Cesium.JulianDate()); - return Cesium.JulianDate.toIso8601(dataZone8).slice(0, 10); - }; + viewer.scene.debugShowFramesPerSecond = true; - //高德卫星地图 - const gaoDeSatelliteImgLayer = new Cesium.UrlTemplateImageryProvider({ - maximumLevel: 18, - minimumLevel: 3, - tilingScheme: new Cesium.WebMercatorTilingScheme(), - url: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}', - }); + _configureMapTile(viewer); - const customLayerViewModel = new Cesium.ProviderViewModel({ - category: 'Cesium ion', // 或 'Other 、Cesium ion'、'Bing Maps' 等 - creationFunction: function () { - return gaoDeSatelliteImgLayer; - }, - iconUrl: 'gaodeImage.png', - name: '高德地图', - tooltip: '高德地图', - }); - // 设置高德地图为默认图层 - viewer.baseLayerPicker.viewModel.imageryProviderViewModels.unshift(customLayerViewModel); - const selectedViewModel = viewer.baseLayerPicker.viewModel.imageryProviderViewModels[0]; - viewer.baseLayerPicker.viewModel.selectedImagery = selectedViewModel; + return viewer; +} +function _configureCesium() { + /* 时间日期格式化 */ { + const minutes = 0 - new Date().getTimezoneOffset(); // 0 - (-480); + + // Animation 的时间日期格式化 + Cesium.AnimationViewModel.defaultDateFormatter = function (date) { + const dataZone8 = Cesium.JulianDate.addMinutes(date, minutes, new Cesium.JulianDate()); + return Cesium.JulianDate.toIso8601(dataZone8).slice(0, 10); + }; + Cesium.AnimationViewModel.defaultTimeFormatter = function (time) { + const dataZone8 = Cesium.JulianDate.addMinutes(time, minutes, new Cesium.JulianDate()); + return Cesium.JulianDate.toIso8601(dataZone8).slice(11, 19); + }; + + // Timeline 的时间日期格式化 + // @ts-expect-error node_modules/@cesium/widgets/Source/Timeline/Timeline.js + Cesium.Timeline.prototype.makeLabel = function (time) { + const dataZone8 = Cesium.JulianDate.addMinutes(time, minutes, new Cesium.JulianDate()); + return Cesium.JulianDate.toIso8601(dataZone8).slice(0, 19); + }; + } + + // 默认视图区域 Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees( 75.0, // 西经 10.0, // 南纬 140.0, // 东经 60.0, // 北纬 ); - - return viewer; +} + +function _configureMapTile(viewer: Cesium.Viewer) { + const provider = new Cesium.UrlTemplateImageryProvider({ + maximumLevel: 18, + minimumLevel: 3, + url: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}', + }); + + if (!viewer.baseLayerPicker) { + // 如果没有底图选择器 + viewer.imageryLayers.removeAll(); + viewer.imageryLayers.addImageryProvider(provider); + } else { + // 如果有底图选择器 + const customLayerViewModel = new Cesium.ProviderViewModel({ + category: 'Cesium ion', // 或 'Other 、Cesium ion'、'Bing Maps' 等 + creationFunction: function () { + return provider; + }, + iconUrl: 'gaodeImage.png', + name: '高德地图', + tooltip: '高德地图', + }); + // 设置高德地图为默认图层 + viewer.baseLayerPicker.viewModel.imageryProviderViewModels.unshift(customLayerViewModel); + const selectedViewModel = viewer.baseLayerPicker.viewModel.imageryProviderViewModels[0]; + viewer.baseLayerPicker.viewModel.selectedImagery = selectedViewModel; + } +} + +function _flyToDemo(viewer: Cesium.Viewer) { + // 将三维球定位到中国 + viewer.camera.flyTo({ + complete: function complete() {}, + destination: Cesium.Cartesian3.fromDegrees(103.84, 31.15, 17850000), + orientation: { + heading: Cesium.Math.toRadians(348.4202942851978), + pitch: Cesium.Math.toRadians(-89.74026687972041), + roll: Cesium.Math.toRadians(0), + }, + }); } diff --git a/src/pages/cesium/cesium-helper/01.x.ts b/src/pages/cesium/cesium-helper/demo_01_OrbitGeneration.ts similarity index 97% rename from src/pages/cesium/cesium-helper/01.x.ts rename to src/pages/cesium/cesium-helper/demo_01_OrbitGeneration.ts index bdf33a9..f094458 100644 --- a/src/pages/cesium/cesium-helper/01.x.ts +++ b/src/pages/cesium/cesium-helper/demo_01_OrbitGeneration.ts @@ -3,7 +3,7 @@ import type { Viewer } from 'cesium'; import * as Cesium from 'cesium'; import { eciToEcf, gstime, propagate, twoline2satrec } from 'satellite.js'; -export async function demoOrbitGeneration(viewer: Viewer) { +export async function demo_01_OrbitGeneration(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`; @@ -16,6 +16,7 @@ export async function demoOrbitGeneration(viewer: Viewer) { // 创建卫星记录 const satrec = twoline2satrec(tleLine1, tleLine2); + console.debug(`satrec :>> `, satrec); // 创建轨道点 const pointsArray = []; diff --git a/src/pages/cesium/cesium-helper/demo_02_Track.ts b/src/pages/cesium/cesium-helper/demo_02_Track.ts new file mode 100644 index 0000000..00f6129 --- /dev/null +++ b/src/pages/cesium/cesium-helper/demo_02_Track.ts @@ -0,0 +1,214 @@ +import type { Viewer } from 'cesium'; + +import * as Cesium from 'cesium'; +import { eciToEcf, gstime, propagate, twoline2satrec } from 'satellite.js'; + +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'); + 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[] = []; + + // 保存卫星高度数据,用于覆盖范围计算 + let satelliteAltitude = 550000; // 默认高度为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') { + 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); + } else { + console.error('Error calculating satellite position'); + } + } + + // 添加完整轨道线 + 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.0, + }), + positions: orbitPositions, + width: 1, + }, + }); + + // 设置卫星的位置 + satelliteEntity.position = positionProperty; + satelliteEntity.orientation = new Cesium.VelocityOrientationProperty(positionProperty); + + // 添加卫星覆盖范围 + // Starlink 卫星通常覆盖范围以锥形方式向地面投射 + // 创建一个椭球体来表示卫星的覆盖范围 + const coverageAngle = 45; // 覆盖角度(度) + const coverageRadius = satelliteAltitude * Math.tan(Cesium.Math.toRadians(coverageAngle)); + + // 创建覆盖范围实体 + const coverageEntity = 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(10000, 10000, 10000); + + // 获取当前卫星高度 + 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()); + + // 计算从卫星到地球中心的方向 + const direction = 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.0), + 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 10000; + + // 计算卫星高度 + 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 10000; + + // 计算卫星高度 + 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), + }); + + // 设置相机自动跟踪卫星 + viewer.trackedEntity = satelliteEntity; + + // 开始动画 + viewer.clock.shouldAnimate = true; + + return satelliteEntity; +} diff --git a/src/pages/cesium/index.page.vue b/src/pages/cesium/index.page.vue index 259f08a..131537d 100644 --- a/src/pages/cesium/index.page.vue +++ b/src/pages/cesium/index.page.vue @@ -2,14 +2,16 @@ import type { Viewer } from 'cesium'; import { cesium_init } from './cesium-helper/00.cesium-init'; -import { demoOrbitGeneration } from './cesium-helper/01.x'; +import { demo_01_OrbitGeneration } from './cesium-helper/demo_01_OrbitGeneration'; +import { demo_02_Track } from './cesium-helper/demo_02_Track'; let viewer: Viewer; onMounted(async () => { - viewer = cesium_init(); + viewer = cesium_init(document.getElementById('cesiumContainer')!); Object.assign(globalThis, { viewer }); - demoOrbitGeneration(viewer); + // demo_01_OrbitGeneration(viewer); + demo_02_Track(viewer); }); diff --git a/typed-router.d.ts b/typed-router.d.ts index 830cded..03c1061 100644 --- a/typed-router.d.ts +++ b/typed-router.d.ts @@ -21,7 +21,7 @@ declare module 'vue-router/auto-routes' { 'Root': RouteRecordInfo<'Root', '/', Record, Record>, '$Path': RouteRecordInfo<'$Path', '/:path(.*)', { path: ParamValue }, { path: ParamValue }>, 'AntdV': RouteRecordInfo<'AntdV', '/AntdV', Record, Record>, - 'API': RouteRecordInfo<'API', '/API', Record, Record>, + 'Api': RouteRecordInfo<'Api', '/api', Record, Record>, 'Cesium': RouteRecordInfo<'Cesium', '/cesium', Record, Record>, 'DataLoadersId': RouteRecordInfo<'DataLoadersId', '/data-loaders/:id', { id: ParamValue }, { id: ParamValue }>, 'DataLoadersIdSub1UserId': RouteRecordInfo<'DataLoadersIdSub1UserId', '/data-loaders/:id/sub-1/:userId', { id: ParamValue, userId: ParamValue }, { id: ParamValue, userId: ParamValue }>,