h-cesium-viewer
Some checks failed
/ build-and-deploy-to-vercel (push) Successful in 2m45s
/ lint-build-and-check (push) Successful in 4m32s
/ playwright (push) Failing after 12m4s
/ surge (push) Successful in 2m34s

This commit is contained in:
严浩
2025-04-02 16:38:02 +08:00
parent 098a769dbd
commit 02ce0fa9a0
21 changed files with 95 additions and 367 deletions

View File

@ -1,33 +0,0 @@
import type { Viewer } from 'cesium';
import * as Cesium from 'cesium';
export const VIEWER_OPTIONS = (): Viewer.ConstructorOptions => {
return {
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: false,
fullscreenButton: !true, // 全屏按钮
geocoder: false, // = 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

@ -1,34 +0,0 @@
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

@ -1,78 +0,0 @@
import * as Cesium from 'cesium';
import { VIEWER_OPTIONS } from './00.cesium-init.VIEWER_OPTIONS';
import 'cesium/Build/Cesium/Widgets/widgets.css';
Cesium.Ion.defaultAccessToken = import.meta.env.VITE_CESIUM_ION_TOKEN; // 用了离线地图的情况是不需要的。
Object.assign(globalThis, { Cesium });
_configureCesium();
export function cesium_init(container: Element) {
const viewer = new Cesium.Viewer(container, VIEWER_OPTIONS());
viewer.scene.debugShowFramesPerSecond = true;
initTimeLine(viewer);
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, // 北纬
);
}
function initTimeLine(viewer: Cesium.Viewer, totalSeconds = /* 默认场景的时间跨度 */ 24 * 60 * 60) {
const start = Cesium.JulianDate.fromIso8601(new Date().toISOString());
const stop = Cesium.JulianDate.addSeconds(start, totalSeconds, new Cesium.JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.timeline.zoomTo(start, stop);
viewer.clock.multiplier = 1;
viewer.clock.shouldAnimate = true;
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
}

View File

@ -1,39 +0,0 @@
## 配置项目
- 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
## 参考
- [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
## TLE
### 格式
- https://celestrak.org/NORAD/documentation/tle-fmt.php
- https://en.wikipedia.org/wiki/Two-line_element_set#Format
### 轨道数据
- https://celestrak.org/NORAD/elements/
- https://www.n2yo.com/satellites/?c=PRC&t=country
- https://www.space-track.org/#catalog
### 相关
- https://www.satview.org/?sat_id=63158U

View File

@ -1,20 +0,0 @@
/* prettier-ignore */
export const TOOLTIP_MAP = {
NORAD_CAT_ID_卫星编号: "5位数字如'63158'表示NORAD卫星唯一目录编号",
CLASSIFICATION_TYPE_卫星分类: "1个字母如'U',表示数据分类",
OBJECT_ID_国际标识符: "8个字符如'25045B'(年份+发射编号+部件),不足时右侧补空格",
EPOCH_历元时间: "14位数字如'25071.65907894',表示轨道数据的参考时间(年份+年积日.一天中的小数部分)",
MEAN_MOTION_DOT_平均运动一阶导数: "10位数字如'.00072212',表示平均运动变化率的一半,单位圈/天²,左侧补空格",
MEAN_MOTION_DDOT_平均运动二阶导数: "8位科学计数法如'-50502-6'表示-5.0502×10⁻⁶圈/天³,最后一位是指数",
BSTAR_BSTAR拖曳项: "BSTAR参数。8位科学计数法如'39937-3'表示0.39937×10⁻³/地球半径,表示大气拖曳影响",
EPHEMERIS_TYPE_星历类型: "通常为'0'表示使用SGP4星历模型",
ELEMENT_SET_NO_元素集编号: "4位数字如'9999'表示TLE版本号",
// CHECKSUM_校验和: "1位数字用于验证数据完整性可选项",
INCLINATION_轨道倾角: "8位数字如'19.0363'表示轨道与赤道平面的夹角范围0°-180°",
RA_OF_ASC_NODE_升交点赤经: "8位数字如'63.0294'表示轨道升交点的赤经范围0°-360°",
ECCENTRICITY_离心率: "7位数字无小数点如'7375486'表示0.7375486,小数点隐含在最前面",
ARG_OF_PERICENTER_近地点幅角: "8位数字如'181.9338'表示近地点相对升交点的角度范围0°-360°",
MEAN_ANOMALY_平近点角: "8位数字如'171.6150'表示历元时的平近点角范围0°-360°",
MEAN_MOTION_平均运动: "11位数字如'2.21786616',表示卫星每天绕地球的圈数(圈/天)",
REV_AT_EPOCH_历元时的圈数: "5位数字如'13'表示卫星在历元时完成的轨道圈数最后以为是校验和共6位",
};

View File

@ -1,170 +0,0 @@
<script setup lang="ts">
import type { Entity, Viewer } from 'cesium';
import { FilterOutlined, SearchOutlined } from '@ant-design/icons-vue';
import { computed, reactive, ref, watchEffect } from 'vue';
import SatelliteEntity from '../cesium-helper/SatelliteEntity';
const { tleList, viewer } = defineProps<{
tleList: string[];
viewer: null | Viewer;
}>();
// 提取卫星名称的函数
const getSatelliteName = (tle: string) => {
return (tle.split('\n') as [string, string, string])[0].trim();
};
// 将satellites改为计算属性响应tleList的变化
const satellites = computed(() => [...new Set(tleList)]);
// 创建Map存储卫星实体
const satelliteEntities = ref<Map<string, Entity>>(new Map());
// 创建Set存储已选中的卫星TLE
const selectedSatellites = ref<Set<string>>(new Set());
const searchText = ref('');
const state = reactive({
checkAll: false,
indeterminate: false,
});
// 计算属性
const selectedCount = computed(() => selectedSatellites.value.size);
const totalCount = computed(() => satellites.value.length);
// 过滤后的卫星列表 - 添加记忆化以提高性能
const filteredSatellites = computed(() => {
if (!searchText.value) return satellites.value;
const searchLower = searchText.value.toLowerCase();
return satellites.value.filter((tle) => getSatelliteName(tle).toLowerCase().includes(searchLower));
});
// 判断卫星是否被选中
const isSatelliteSelected = (tle: string) => {
return selectedSatellites.value.has(tle);
};
// 监听选中状态变化,更新全选和半选状态
watchEffect(() => {
const count = selectedCount.value;
const filteredCount = filteredSatellites.value.length;
state.indeterminate = count > 0 && count < filteredCount;
state.checkAll = count === filteredCount && filteredCount > 0;
});
// 更新卫星实体(添加或移除)
const updateSatelliteEntity = (tle: string, selected: boolean) => {
if (!viewer) return;
const satelliteName = getSatelliteName(tle);
if (selected) {
// 添加卫星到viewer
try {
const satelliteObject = new SatelliteEntity(tle);
const cesiumSateEntity = satelliteObject.createSatelliteEntity();
const result = viewer.entities.add(cesiumSateEntity);
satelliteEntities.value.set(tle, result);
} catch (error) {
console.error(`添加卫星 ${satelliteName} 失败:`, error);
}
} else {
// 从viewer中移除卫星
const entity = satelliteEntities.value.get(tle);
if (entity) {
viewer.entities.remove(entity);
satelliteEntities.value.delete(tle);
}
}
};
// 切换卫星选中状态
const toggleSatellite = (tle: string) => {
const isSelected = isSatelliteSelected(tle);
if (isSelected) {
selectedSatellites.value.delete(tle);
} else {
selectedSatellites.value.add(tle);
}
updateSatelliteEntity(tle, !isSelected);
};
// 全选/取消全选
const onCheckAllChange = (e: { target: { checked: boolean } }) => {
const checked = e.target.checked;
for (const tle of filteredSatellites.value) {
const isCurrentlySelected = isSatelliteSelected(tle);
if (isCurrentlySelected !== checked) {
if (checked) {
selectedSatellites.value.add(tle);
} else {
selectedSatellites.value.delete(tle);
}
updateSatelliteEntity(tle, checked);
}
}
};
</script>
<template>
<div class="SatelliteSelectorPanel">
<APopover :overlay-style="{ width: '320px' }" placement="bottomLeft" trigger="click">
<template #title>
选择卫星
<span class="text-xs font-normal text-gray-700 dark:text-gray-400">{{
`(已选择: ${selectedCount}/${totalCount})`
}}</span>
</template>
<template #content>
<!-- 搜索框 -->
<AInput v-model:value="searchText" allow-clear placeholder="搜索卫星">
<template #prefix><SearchOutlined /></template>
</AInput>
<ADivider class="my-3" />
<!-- 无结果提示 -->
<div v-if="filteredSatellites.length === 0" class="py-4 text-center text-gray-500">没有找到匹配的卫星</div>
<!-- 全选选项 -->
<ACheckbox
v-if="filteredSatellites.length > 0"
:checked="state.checkAll"
:indeterminate="state.indeterminate"
@change="onCheckAllChange"
>
全选
</ACheckbox>
<!-- 卫星列表 -->
<div class="satellite-list max-h-60 overflow-y-auto pr-1">
<ACheckbox
v-for="tle in filteredSatellites"
:key="tle"
:checked="isSatelliteSelected(tle)"
class="my-2 flex w-full"
@change="() => toggleSatellite(tle)"
>
{{ getSatelliteName(tle) }}
</ACheckbox>
</div>
</template>
<!-- 触发按钮 -->
<AButton>
<template #icon><FilterOutlined /></template>
<span v-if="selectedCount > 0" class="ml-1">({{ selectedCount }})</span>
</AButton>
</APopover>
</div>
</template>

View File

@ -1,78 +0,0 @@
<script setup lang="ts">
import type { Viewer } from 'cesium';
import { TLE_LIST } from './cesium-demos/_TLE_DATA';
import { demo_卫星加站点 } from './cesium-demos/demo_03_卫星加站点';
import { cesium_init } from './cesium-helper/00.cesium-init';
import SatelliteSelector from './components/SatelliteSelector.vue';
// Cesium相关
let viewer = $ref<null | Viewer>(null);
const tleList = ref([TLE_LIST[0]] as string[]);
const spinning = ref(false);
// 模拟接口获取TLE数据
const fetchTLEData = async () => {
spinning.value = true; // 开始加载设置状态为true
await new Promise((resolve) => setTimeout(resolve, 2000)); // 模拟2秒延迟
tleList.value = TLE_LIST.slice(0, 2); // 设置TLE数据
spinning.value = false; // 加载完成设置状态为false
};
// 初始化Cesium
onMounted(async () => {
fetchTLEData();
const container = document.querySelector('#cesiumContainer');
if (!container) return;
viewer = cesium_init(container);
Object.assign(globalThis, { viewer });
demo_卫星加站点(viewer);
});
// 清理资源
onBeforeUnmount(() => {
if (viewer) {
// 销毁viewer
viewer.destroy();
viewer = null;
}
});
</script>
<template>
<div id="cesiumContainer" class="absolute top-0 left-0 right-0 bottom-0">
<ASpin
size="large"
v-if="spinning"
class="z-101 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
/>
<div class="z-100 absolute left-4 top-4 flex flex-col gap-4">
<SatelliteSelector :viewer="viewer" :tle-list />
<AButton
@click="
() => {
tleList = TLE_LIST;
}
"
>
<template #icon> <ReloadOutlined /> </template>3 个卫星
</AButton>
<AButton
@click="
() => {
tleList = TLE_LIST.splice(0, 2);
}
"
>
<template #icon> <ReloadOutlined /> </template>2 个卫星
</AButton>
</div>
</div>
</template>

View File

@ -1 +0,0 @@
- https://satnogs.org

View File

@ -5,19 +5,26 @@ import { eciToEcf, gstime, propagate, twoline2satrec } from 'satellite.js';
/**
* 230
*/
export async function demo_02_Track(viewer: Viewer) {
const tle = `STARLINK-11371 [DTC]
export async function demo_02_Track(
viewer: Viewer,
tle = `DEMO [测试]
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`;
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 satelliteColor = Cesium.Color.fromRandom({ alpha: 1 });
const pathColor = Cesium.Color.fromRandom({ alpha: 1 });
const orbitColor = Cesium.Color.fromRandom({ alpha: 1 });
const coverageColor = Cesium.Color.fromRandom({ alpha: 1 });
// 创建卫星实体
const satelliteEntity = viewer.entities.add({
id: 'STARLINK-11371',
id: `${satelliteName}-satellite`,
label: {
fillColor: Cesium.Color.WHITE,
font: '14pt sans-serif',
@ -28,11 +35,11 @@ export async function demo_02_Track(viewer: Viewer) {
text: satelliteName,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
},
name: 'STARLINK-11371',
name: `${satelliteName} Satellite`,
// 卫星轨迹
path: {
material: new Cesium.PolylineGlowMaterialProperty({
color: Cesium.Color.BLUE,
color: pathColor,
glowPower: 0.2,
}),
resolution: 1,
@ -40,7 +47,7 @@ export async function demo_02_Track(viewer: Viewer) {
},
// 使用简单点图形表示卫星
point: {
color: Cesium.Color.YELLOW,
color: satelliteColor,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
pixelSize: 10,
@ -102,12 +109,12 @@ export async function demo_02_Track(viewer: Viewer) {
// 添加完整轨道线
viewer.entities.add({
id: 'STARLINK-11371-orbit',
name: 'STARLINK-11371 Full Orbit',
id: `${satelliteName}-orbit`,
name: `${satelliteName} Full Orbit`,
polyline: {
clampToGround: false,
material: new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.CYAN,
color: orbitColor,
dashLength: 8,
}),
positions: orbitPositions,
@ -129,9 +136,9 @@ export async function demo_02_Track(viewer: Viewer) {
// 创建覆盖范围实体
viewer.entities.add({
ellipsoid: {
material: Cesium.Color.BLUE.withAlpha(0.2),
material: coverageColor.withAlpha(0.2),
outline: true,
outlineColor: Cesium.Color.BLUE.withAlpha(0.8),
outlineColor: coverageColor.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);
@ -149,8 +156,8 @@ export async function demo_02_Track(viewer: Viewer) {
slicePartitions: 24,
stackPartitions: 16,
},
id: 'STARLINK-11371-coverage',
name: 'STARLINK-11371 Coverage',
id: `${satelliteName}-coverage`,
name: `${satelliteName} Coverage`,
orientation: new Cesium.CallbackProperty(() => {
// 确保椭球体始终指向地球中心
const position = satelliteEntity.position?.getValue(viewer.clock.currentTime);
@ -169,9 +176,9 @@ export async function demo_02_Track(viewer: Viewer) {
ellipse: {
granularity: Cesium.Math.toRadians(1),
height: 0,
material: Cesium.Color.BLUE.withAlpha(0.2),
material: coverageColor.withAlpha(0.2),
outline: true,
outlineColor: Cesium.Color.BLUE.withAlpha(0.8),
outlineColor: coverageColor.withAlpha(0.8),
outlineWidth: 2,
semiMajorAxis: new Cesium.CallbackProperty(() => {
const satPosition = satelliteEntity.position?.getValue(viewer.clock.currentTime);
@ -192,8 +199,8 @@ export async function demo_02_Track(viewer: Viewer) {
return cartographic.height * Math.tan(Cesium.Math.toRadians(coverageAngle));
}, false),
},
id: 'STARLINK-11371-ground-coverage',
name: 'STARLINK-11371 Ground Coverage',
id: `${satelliteName}-coverage-ground`,
name: `${satelliteName} Ground Coverage`,
position: new Cesium.CallbackPositionProperty(() => {
const satPosition = satelliteEntity.position?.getValue(viewer.clock.currentTime);
if (!satPosition) return new Cesium.Cartesian3();
@ -208,7 +215,7 @@ export async function demo_02_Track(viewer: Viewer) {
// - 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.trackedEntity = satelliteEntity;
// 开始动画
viewer.clock.shouldAnimate = true;