整理
Some checks failed
/ depcheck (push) Successful in 2m24s
/ lint-build-and-check (push) Successful in 2m36s
/ build-and-deploy-to-vercel (push) Successful in 3m13s
/ surge (push) Successful in 2m40s
/ playwright (push) Failing after 8m16s

This commit is contained in:
严浩
2025-03-10 12:41:24 +08:00
parent a7b10809c1
commit 4542944f52
35 changed files with 66 additions and 60 deletions

View File

@ -0,0 +1,31 @@
import type { Viewer } from 'cesium';
import * as Cesium from 'cesium';
export const VIEWER_OPTIONS: Viewer.ConstructorOptions = {
animation: true, // .cesium-viewer-animationContainer https://cesium.com/learn/ion-sdk/ref-doc/Animation.html
baseLayer: Cesium.ImageryLayer.fromProviderAsync(
Cesium.TileMapServiceImageryProvider.fromUrl(Cesium.buildModuleUrl('Assets/Textures/NaturalEarthII')),
),
baseLayerPicker: !true,
fullscreenButton: !true, // 全屏按钮
geocoder: !true, // = IonGeocodeProviderType.DEFAULT] - 在使用Geocoder小部件进行搜索时使用的地理编码服务或服务。如果设置为false则不会创建Geocoder小部件。
// globe: false, // 地球
homeButton: true, // Home按钮
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/|使用显式渲染提高性能}。
sceneModePicker: true, // 是否显示场景模式选择器(2D/3D切换)
selectionIndicator: true,
shadows: true, // Determines if shadows are cast by light sources.
/* 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,
showRenderLoopErrors: true, // 如果为真当发生渲染循环错误时此小部件将自动向用户显示包含错误的HTML面板。
timeline: true,
};

View File

@ -0,0 +1,34 @@
import * as Cesium from 'cesium';
const provider = new Cesium.UrlTemplateImageryProvider({
maximumLevel: 18,
minimumLevel: 3,
url: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
});
export function configureMapTile(viewer: Cesium.Viewer) {
if (viewer.baseLayerPicker) {
// 如果有底图选择器
const customLayerViewModel = new Cesium.ProviderViewModel({
category: 'Cesium ion', // 或 'Other 、Cesium ion'、'Bing Maps' 等
creationFunction() {
return provider;
},
iconUrl: 'gaodeImage.png',
name: '高德地图',
tooltip: '高德地图',
});
// 设置高德地图为默认图层
viewer.baseLayerPicker.viewModel.imageryProviderViewModels.unshift(customLayerViewModel);
const selectedViewModel = viewer.baseLayerPicker.viewModel.imageryProviderViewModels[0];
if (!selectedViewModel) {
console.error('未找到默认底图');
return;
}
viewer.baseLayerPicker.viewModel.selectedImagery = selectedViewModel;
} else {
// 如果没有底图选择器
viewer.imageryLayers.removeAll();
viewer.imageryLayers.addImageryProvider(provider);
}
}

View File

@ -0,0 +1,62 @@
import * as Cesium from 'cesium';
import { VIEWER_OPTIONS } from './00.cesium-init.VIEWER_OPTIONS';
import 'cesium/Build/Cesium/Widgets/widgets.css';
Object.assign(window, { Cesium });
_configureCesium();
export function cesium_init(container: Element) {
const viewer = new Cesium.Viewer(container, VIEWER_OPTIONS);
viewer.scene.debugShowFramesPerSecond = true;
return viewer;
}
function _configureCesium() {
if (document.querySelector('#hide-cesium-viewer-bottom') === null) {
document.head.append(
Object.assign(document.createElement('style'), {
id: 'hide-cesium-viewer-bottom',
innerHTML: `
.cesium-viewer-bottom {
display: none !important;
}
`.trim(),
type: 'text/css',
}),
);
}
/* 时间日期格式化 */ {
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, // 西经
10, // 南纬
140, // 东经
60, // 北纬
);
}

View File

@ -0,0 +1,20 @@
## 配置项目
- https://github.dev/CesiumGS/cesium-vite-example
- https://cesium.com/blog/2024/02/13/configuring-vite-or-webpack-for-cesiumjs/
- https://cesium.com/learn/cesiumjs-learn/cesiumjs-quickstart/
- vite-plugin-cesium
## 参考
- https://www.npmjs.com/package/vue-cesium
- https://zouyaoji.top/vue-cesium/#/zh-CN/component/controls/vc-navigation
- https://cesium.pages.dev/
## 离线地图
- https://github.com/CesiumGS/cesium/tree/main/Documentation/OfflineGuide
- https://blog.csdn.net/lhllhllhl_/article/details/145857779
- https://blog.csdn.net/m0_54849806/article/details/126070809
- https://juejin.cn/post/6969838266182795278
- https://blog.csdn.net/CSDNqinzhike/article/details/139587028

View File

@ -0,0 +1,115 @@
import type { Viewer } from 'cesium';
import * as Cesium from 'cesium';
import { eciToEcf, gstime, propagate, twoline2satrec } from 'satellite.js';
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`;
// 解析TLE数据
const lines = tle.split('\n') as [string, string, string];
const satelliteName = lines[0].trim();
const tleLine1 = lines[1];
const tleLine2 = lines[2];
// 创建卫星记录
const satrec = twoline2satrec(tleLine1, tleLine2);
console.debug(`satrec :>>`, satrec);
// 创建轨道点
const pointsArray = [];
const totalMinutes = 1440; // 一天的分钟数
const timeStepInMinutes = 10;
// 当前时间
const now = new Date();
await new Promise((resolve) => setTimeout(resolve, 500));
// 计算一条完整的轨道
for (let i = 0; i < totalMinutes; i += timeStepInMinutes) {
// 创建时间点
const time = new Date(now.getTime() + i * 60_000);
// 获取卫星位置
const positionAndVelocity = propagate(satrec, time);
// 有时可能返回false
const position = positionAndVelocity.position;
if (typeof position === 'boolean') {
console.error('Error calculating satellite position');
return;
}
// 转换为千米
const gmst = gstime(time);
const p = eciToEcf(position, gmst);
// 添加到点数组
pointsArray.push(
Cesium.Cartesian3.fromDegrees(
Cesium.Math.toDegrees(Math.atan2(p.y, p.x)),
Cesium.Math.toDegrees(Math.atan2(p.z, Math.hypot(p.x, p.y))),
Math.hypot(p.x, p.y, p.z) * 1000, // 转换为米
),
);
}
// 获取当前位置用于放置卫星模型
const time = new Date();
const positionAndVelocity = propagate(satrec, time);
const position = positionAndVelocity.position;
const gmst = gstime(time);
if (typeof position === 'boolean') {
console.error('Error calculating satellite position');
return;
}
const p = eciToEcf(position, gmst);
const currentPosition = Cesium.Cartesian3.fromDegrees(
Cesium.Math.toDegrees(Math.atan2(p.y, p.x)),
Cesium.Math.toDegrees(Math.atan2(p.z, Math.hypot(p.x, p.y))),
Math.hypot(p.x, p.y, p.z) * 1000, // 转换为米
);
viewer.entities.add({
name: `${satelliteName} Orbit`,
polyline: {
material: new Cesium.PolylineGlowMaterialProperty({
color: Cesium.Color.BLUE,
glowPower: 0.2,
}),
positions: pointsArray,
width: 2,
},
});
// 添加卫星实体
const satellite = viewer.entities.add({
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: satelliteName,
point: {
color: Cesium.Color.YELLOW,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
pixelSize: 10,
},
position: currentPosition,
// billboard: {
// image: '/assets/satellite.png', // 您需要添加一个卫星图标
// scale: 0.5,
// },
});
// 将相机定位到卫星
viewer.flyTo(satellite, { duration: 3 });
}

View File

@ -0,0 +1,217 @@
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,
},
// viewFrom: new Cartesian3(0, -1.5e7, 2.5e7),
// https://www.google.com/search?q=cesium%C2%A0viewFrom%C2%A0+trackedEntity
});
// 计算并绘制卫星轨迹
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;
}

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import type { Viewer } from 'cesium';
import { cesium_init } from './cesium-helper/00.cesium-init';
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(document.getElementById('cesiumContainer')!);
Object.assign(globalThis, { viewer });
(({ run }) => (run ? demo_01_OrbitGeneration(viewer) : null))({ run: false });
(({ run }) => (run ? demo_02_Track(viewer) : null))({ run: true });
});
</script>
<template>
<div id="cesiumContainer"></div>
</template>
<style>
#cesiumContainer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.cesium-viewer-bottom {
display: none;
}
</style>