feat: 更新地面站和卫星状态管理为使用 Set
Some checks failed
/ build-and-deploy-to-vercel (push) Successful in 3m13s
/ lint-build-and-check (push) Successful in 4m38s
/ playwright (push) Failing after 12m17s
/ surge (push) Successful in 2m47s

This commit is contained in:
严浩
2025-04-02 19:09:00 +08:00
parent 02ce0fa9a0
commit 7261a45cab
12 changed files with 180 additions and 68 deletions

View File

@ -1,15 +1,27 @@
import * as Cesium from 'cesium';
import { eciToEcf, gstime, propagate, type SatRec, twoline2satrec } from 'satellite.js';
import {
eciToEcf,
gstime,
propagate,
type SatRec,
twoline2satrec,
} from 'satellite.js';
import type { GroundStationOptions, SatelliteOptions } from './h-cesium-viewer-class.types'; // 2小时
import type {
GroundStationOptions,
SatelliteOptions,
} from './h-cesium-viewer-class.types';
import { VIEWER_OPTIONS_FN } from './helper/_VIEWER_OPTIONS';
import { configureCesium } from './helper/configureCesium';
import { configureTimeLine } from './helper/configureTimeLine';
const = 2 * 60 * 60;
const = 2 * 60 * 60; // 2小时
export { type GroundStationOptions, type SatelliteOptions } from './h-cesium-viewer-class.types';
export {
type GroundStationOptions,
type SatelliteOptions,
} from './h-cesium-viewer-class.types';
Cesium.Ion.defaultAccessToken = import.meta.env.VITE_CESIUM_ION_TOKEN; // 用了离线地图的情况是不需要的。
@ -67,12 +79,19 @@ export class HCesiumViewerCls {
}
// 解构赋值获取站点信息
const { height = 0, id, latitude, longitude, name, pixelSize = 10 } = options;
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,
id,
label: {
font: '14pt sans-serif',
outlineWidth: 2,
@ -81,14 +100,14 @@ export class HCesiumViewerCls {
text: name,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
},
name: name,
name,
point: {
color: Cesium.Color.fromRandom(), // 随机颜色
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
pixelSize,
},
position: position,
position,
});
const addedEntity = this.viewer.entities.add(groundStationEntity);
@ -161,9 +180,11 @@ export class HCesiumViewerCls {
} = options;
// --- 从 tle 字符串解析 name, tle1, tle2 ---
const tleLines = tle.trim().split('\n');
const tleLines = tle.trim().split('\n') as [string, string, string];
if (tleLines.length < 3) {
console.error(`无效的 TLE 格式 (ID: ${id}): TLE 字符串 "${tle}" 至少需要三行`);
console.error(
`无效的 TLE 格式 (ID: ${id}): TLE 字符串 "${tle}" 至少需要三行`,
);
return null;
}
const name = tleLines[0].trim();
@ -185,8 +206,8 @@ export class HCesiumViewerCls {
// 创建卫星实体
const satelliteEntity = this.viewer.entities.add({
id: id,
name: name,
id,
name,
label: {
text: name,
font: '14pt sans-serif',
@ -223,7 +244,11 @@ export class HCesiumViewerCls {
const orbitPositions: Cesium.Cartesian3[] = []; // 用于存储完整轨道点
for (let i = 0; i <= orbitDurationSeconds; i += timeStepSeconds) {
const time = Cesium.JulianDate.addSeconds(startTime, i, new Cesium.JulianDate());
const time = Cesium.JulianDate.addSeconds(
startTime,
i,
new Cesium.JulianDate(),
);
const jsDate = Cesium.JulianDate.toDate(time);
try {
@ -238,7 +263,11 @@ export class HCesiumViewerCls {
const positionEcf = eciToEcf(positionAndVelocity.position, gmst);
// 转换为 Cesium 坐标(单位:米)
const cesiumPosition = new Cesium.Cartesian3(positionEcf.x * 1000, positionEcf.y * 1000, positionEcf.z * 1000);
const cesiumPosition = new Cesium.Cartesian3(
positionEcf.x * 1000,
positionEcf.y * 1000,
positionEcf.z * 1000,
);
// 添加位置样本
positionProperty.addSample(time, cesiumPosition);
@ -254,7 +283,9 @@ export class HCesiumViewerCls {
// 设置卫星的位置和方向
satelliteEntity.position = positionProperty;
satelliteEntity.orientation = new Cesium.VelocityOrientationProperty(positionProperty);
satelliteEntity.orientation = new Cesium.VelocityOrientationProperty(
positionProperty,
);
// --- 添加卫星地面覆盖范围 ---
// 使用 CallbackProperty 动态计算星下点位置
@ -266,7 +297,8 @@ export class HCesiumViewerCls {
return; // 如果位置无效,则不返回任何内容
}
// 转换为地理坐标(包含高度)
const satelliteCartographic = Cesium.Cartographic.fromCartesian(satelliteCartesian);
const satelliteCartographic =
Cesium.Cartographic.fromCartesian(satelliteCartesian);
if (!satelliteCartographic) {
return; // 如果转换失败,则不返回任何内容
}
@ -277,7 +309,11 @@ export class HCesiumViewerCls {
0,
);
// 转换回笛卡尔坐标
return Cesium.Cartographic.toCartesian(subsatelliteCartographic, Cesium.Ellipsoid.WGS84, result);
return Cesium.Cartographic.toCartesian(
subsatelliteCartographic,
Cesium.Ellipsoid.WGS84,
result,
);
},
false,
Cesium.ReferenceFrame.FIXED,
@ -289,7 +325,8 @@ export class HCesiumViewerCls {
if (!satelliteCartesian) {
return 100_000; // 默认半径 100km
}
const satelliteCartographic = Cesium.Cartographic.fromCartesian(satelliteCartesian);
const satelliteCartographic =
Cesium.Cartographic.fromCartesian(satelliteCartesian);
if (!satelliteCartographic) {
return 100_000;
}
@ -336,7 +373,11 @@ export class HCesiumViewerCls {
}
// 存储实体引用
this.currentSatelliteEntities.set(id, { entity: satelliteEntity, orbitEntity, coverageEntity });
this.currentSatelliteEntities.set(id, {
entity: satelliteEntity,
orbitEntity,
coverageEntity,
});
return satelliteEntity;
}
@ -361,7 +402,9 @@ export class HCesiumViewerCls {
if (satelliteData.entity) {
removedMain = this.viewer.entities.remove(satelliteData.entity);
if (!removedMain) {
console.warn(`尝试从 Cesium 移除卫星主体 ID 为 "${entityId}" 的实体失败。`);
console.warn(
`尝试从 Cesium 移除卫星主体 ID 为 "${entityId}" 的实体失败。`,
);
}
}
@ -369,15 +412,21 @@ export class HCesiumViewerCls {
if (satelliteData.orbitEntity) {
removedOrbit = this.viewer.entities.remove(satelliteData.orbitEntity);
if (!removedOrbit) {
console.warn(`尝试从 Cesium 移除卫星轨道 ID 为 "${satelliteData.orbitEntity.id}" 的实体失败。`);
console.warn(
`尝试从 Cesium 移除卫星轨道 ID 为 "${satelliteData.orbitEntity.id}" 的实体失败。`,
);
}
}
// 移除覆盖范围
if (satelliteData.coverageEntity) {
removedCoverage = this.viewer.entities.remove(satelliteData.coverageEntity);
removedCoverage = this.viewer.entities.remove(
satelliteData.coverageEntity,
);
if (!removedCoverage) {
console.warn(`尝试从 Cesium 移除卫星覆盖范围 ID 为 "${satelliteData.coverageEntity.id}" 的实体失败。`);
console.warn(
`尝试从 Cesium 移除卫星覆盖范围 ID 为 "${satelliteData.coverageEntity.id}" 的实体失败。`,
);
}
}

View File

@ -1,19 +1,19 @@
<script setup lang="ts">
import 'cesium/Build/Cesium/Widgets/widgets.css';
import type { GroundStationState, SatelliteState } from './types';
import { useHCesiumViewerCls } from './useHCesiumViewerCls';
import { useHCesiumViewerClsGroundStation } from './useHCesiumViewerClsGroundStation';
import { useHCesiumViewerClsSatellite } from './useHCesiumViewerClsSatellite';
import 'cesium/Build/Cesium/Widgets/widgets.css';
const props = defineProps<{
groundStationState?: GroundStationState;
satelliteState?: SatelliteState;
}>();
// 1. 管理 Cesium Viewer 实例生命周期
const { hCesiumViewerInst } = useHCesiumViewerCls('cesiumContainer');
const { hCesiumViewerInst } = useHCesiumViewerCls('cesium-container');
// 2. 同步地面站实体
// 将实例的 getter 和 props 的 getter 传递给组合函数
@ -32,14 +32,14 @@ useHCesiumViewerClsSatellite(
</script>
<template>
<div id="cesiumContainer" class="h-full w-full relative inset-0 isolate">
<slot />
<div id="cesium-container" class="relative inset-0 isolate h-full w-full">
<slot></slot>
</div>
</template>
<style scoped>
#cesiumContainer {
min-height: 300px;
#cesium-container {
min-height: 100px;
background-color: #000;
}
</style>

View File

@ -16,5 +16,5 @@ export const TOOLTIP_MAP = {
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位",
REV_AT_EPOCH_历元时的圈数: "5位数字如'13',表示卫星在历元时完成的轨道圈数",
};

View File

@ -6,7 +6,9 @@ export const VIEWER_OPTIONS_FN = (): 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')),
Cesium.TileMapServiceImageryProvider.fromUrl(
Cesium.buildModuleUrl('Assets/Textures/NaturalEarthII'),
),
),
baseLayerPicker: false,
fullscreenButton: !true, // 全屏按钮

View File

@ -20,18 +20,30 @@ export function configureCesium() {
// Animation 的时间日期格式化
Cesium.AnimationViewModel.defaultDateFormatter = function (date) {
const dataZone8 = Cesium.JulianDate.addMinutes(date, minutes, new Cesium.JulianDate());
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());
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());
const dataZone8 = Cesium.JulianDate.addMinutes(
time,
minutes,
new Cesium.JulianDate(),
);
return Cesium.JulianDate.toIso8601(dataZone8).slice(0, 19);
};
}

View File

@ -19,8 +19,11 @@ export function configureMapTile(viewer: Cesium.Viewer) {
tooltip: '高德地图',
});
// 设置高德地图为默认图层
viewer.baseLayerPicker.viewModel.imageryProviderViewModels.unshift(customLayerViewModel);
const selectedViewModel = viewer.baseLayerPicker.viewModel.imageryProviderViewModels[0];
viewer.baseLayerPicker.viewModel.imageryProviderViewModels.unshift(
customLayerViewModel,
);
const selectedViewModel =
viewer.baseLayerPicker.viewModel.imageryProviderViewModels[0];
if (!selectedViewModel) {
console.error('未找到默认底图');
return;

View File

@ -2,9 +2,16 @@ import * as Cesium from 'cesium';
const = 2 * 60 * 60; // 2小时
export function configureTimeLine(viewer: Cesium.Viewer, totalSeconds = ) {
export function configureTimeLine(
viewer: Cesium.Viewer,
totalSeconds = ,
) {
const start = Cesium.JulianDate.fromIso8601(new Date().toISOString());
const stop = Cesium.JulianDate.addSeconds(start, totalSeconds, new Cesium.JulianDate());
const stop = Cesium.JulianDate.addSeconds(
start,
totalSeconds,
new Cesium.JulianDate(),
);
// 设置时钟范围
viewer.clock.startTime = start.clone();

View File

@ -1,4 +1,7 @@
import type { GroundStationOptions, SatelliteOptions } from './h-cesium-viewer-class';
import type {
GroundStationOptions,
SatelliteOptions,
} from './h-cesium-viewer-class';
/**
* 地面站状态接口
@ -7,8 +10,8 @@ import type { GroundStationOptions, SatelliteOptions } from './h-cesium-viewer-c
export interface GroundStationState {
/** 地面站配置数组 */
groundStations: GroundStationOptions[];
/** 选中的地面站 ID 数组 */
selectedIds: string[];
/** 选中的地面站 ID 集合 */
selectedIds: Set<string>;
}
/**
@ -18,6 +21,6 @@ export interface GroundStationState {
export interface SatelliteState {
/** 卫星配置数组 */
satellites: SatelliteOptions[];
/** 选中的卫星 ID 数组 */
selectedIds: string[];
/** 选中的卫星 ID 集合 */
selectedIds: Set<string>;
}

View File

@ -1,5 +1,6 @@
// src/utils/useHCesiumViewerCls.ts
import { HCesiumViewerCls } from '@/components/h-cesium-viewer/h-cesium-viewer-class';
import { HCesiumViewerCls } from './h-cesium-viewer-class';
/**
* 管理 HCesiumViewerCls 实例的生命周期。

View File

@ -1,17 +1,22 @@
// src/utils/useHCesiumViewerClsGroundStation.ts
import type { GroundStationOptions, HCesiumViewerCls } from '@/components/h-cesium-viewer/h-cesium-viewer-class';
import type { MaybeRefOrGetter } from 'vue';
import type {
GroundStationOptions,
HCesiumViewerCls,
} from './h-cesium-viewer-class';
/**
* 管理 Cesium Viewer 中的地面站实体,根据选中的 ID 列表进行同步。
* @param hCesiumViewerInst - HCesiumViewerCls 实例或其 getter。
* @param groundStationList - 包含所有可用地面站选项的数组或 getter。
* @param selectedStationIds - 包含当前选中地面站 ID 的数组或 getter。
* @param selectedStationIds - 包含当前选中地面站 ID 的 Set 或 getter。
*/
export function useHCesiumViewerClsGroundStation(
hCesiumViewerInst: MaybeRefOrGetter<HCesiumViewerCls | null>,
groundStationList: MaybeRefOrGetter<Array<GroundStationOptions> | undefined>,
selectedStationIds: MaybeRefOrGetter<string[] | undefined>,
selectedStationIds: MaybeRefOrGetter<Set<string> | undefined>,
) {
// 创建一个从 ID 到站点选项的映射,方便查找
const stationMap = computed(() => {
@ -31,9 +36,10 @@ export function useHCesiumViewerClsGroundStation(
return;
}
const selectedIds = toValue(selectedStationIds) ?? [];
const selectedIdsSet = new Set(selectedIds); // 需要显示的站点 ID
const currentEntityIds = new Set(viewerInstance.currentStationEntities.keys()); // 当前已显示的站点 ID
const selectedIdsSet = toValue(selectedStationIds) ?? new Set<string>(); // 直接获取 Set如果为 undefined 则创建空 Set
const currentEntityIds = new Set(
viewerInstance.currentStationEntities.keys(),
); // 当前已显示的站点 ID
// 1. 移除不再选中的站点
for (const entityId of currentEntityIds) {
@ -52,7 +58,9 @@ export function useHCesiumViewerClsGroundStation(
viewerInstance.addGroundStation(stationToAdd);
} else {
// 如果在 groundStationList 中找不到对应的站点信息,发出警告
consola.warn(`无法找到 ID 为 "${selectedId}" 的站点信息,无法添加到地图。`);
console.warn(
`无法找到 ID 为 "${selectedId}" 的站点信息,无法添加到地图。`,
);
}
}
}

View File

@ -1,17 +1,22 @@
// src/components/h-cesium-viewer/useHCesiumViewerClsSatellite.ts
import type { HCesiumViewerCls, SatelliteOptions } from '@/components/h-cesium-viewer/h-cesium-viewer-class';
import type { MaybeRefOrGetter } from 'vue';
import type {
HCesiumViewerCls,
SatelliteOptions,
} from './h-cesium-viewer-class';
/**
* 管理 Cesium Viewer 中的卫星实体,根据选中的 ID 列表进行同步。
* @param hCesiumViewerInst - HCesiumViewerCls 实例或其 getter。
* @param satelliteList - 包含所有可用卫星选项的数组或 getter。
* @param selectedSatelliteIds - 包含当前选中卫星 ID 的数组或 getter。
* @param selectedSatelliteIds - 包含当前选中卫星 ID 的 Set 或 getter。
*/
export function useHCesiumViewerClsSatellite(
hCesiumViewerInst: MaybeRefOrGetter<HCesiumViewerCls | null>,
satelliteList: MaybeRefOrGetter<Array<SatelliteOptions> | undefined>,
selectedSatelliteIds: MaybeRefOrGetter<string[] | undefined>,
selectedSatelliteIds: MaybeRefOrGetter<Set<string> | undefined>,
) {
// 创建一个从 ID 到卫星选项的映射,方便查找
const satelliteMap = computed(() => {
@ -31,9 +36,10 @@ export function useHCesiumViewerClsSatellite(
return;
}
const selectedIds = toValue(selectedSatelliteIds) ?? [];
const selectedIdsSet = new Set(selectedIds); // 需要显示的卫星 ID
const currentEntityIds = new Set(viewerInstance.currentSatelliteEntities.keys()); // 当前已显示的卫星 ID
const selectedIdsSet = toValue(selectedSatelliteIds) ?? new Set<string>(); // 直接获取 Set如果为 undefined 则创建空 Set
const currentEntityIds = new Set(
viewerInstance.currentSatelliteEntities.keys(),
); // 当前已显示的卫星 ID
// 1. 移除不再选中的卫星
for (const entityId of currentEntityIds) {
@ -49,10 +55,15 @@ export function useHCesiumViewerClsSatellite(
// 如果选中的 ID 对应的实体当前未显示,则添加
const satelliteToAdd = satelliteMap.value.get(selectedId); // 从映射中查找卫星信息
if (satelliteToAdd) {
console.debug(
`添加卫星 "${satelliteToAdd.id}" 到地图TLE: ${satelliteToAdd.tle}`,
);
viewerInstance.addSatellite(satelliteToAdd);
} else {
// 如果在 satelliteList 中找不到对应的卫星信息,发出警告
consola.warn(`无法找到 ID 为 "${selectedId}" 的卫星信息,无法添加到地图。`);
console.warn(
`无法找到 ID 为 "${selectedId}" 的卫星信息,无法添加到地图。`,
);
}
}
}