Files
xian_vue_new/src/utils/cesium/CesiumUtils.ts
T
2026-06-14 19:50:28 +08:00

782 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { CesiumInitOptions } from '@/types/cesium/CesiumInitOptions';
import type { EntityOptions } from '@/types/cesium/EntityOptions';
import type { PrimitiveOptions } from '@/types/cesium/PrimitiveOptions';
import type { LayerConfig } from '@/types/cesium/LayerConfig';
import type {
CustomizeGeoJsonDataSource,
GeoJsonOptions,
} from '@/types/cesium/GeoJsonOptions';
import {
Viewer,
Entity,
DataSource,
ImageryLayer,
Primitive,
BillboardCollection,
Cartesian3,
ScreenSpaceEventHandler,
ScreenSpaceEventType,
Cartesian2,
SceneTransforms,
Rectangle,
Color,
JulianDate,
CallbackProperty,
HeightReference,
VerticalOrigin,
HorizontalOrigin,
} from 'cesium';
import { CesiumViewerManager } from './CesiumViewerManager';
import { EntityManager } from './EntityManager';
import { PrimitiveManager } from './PrimitiveManager';
import { LayerManager } from './LayerManager';
import { GeoJsonManager, type ClearType } from './GeoJsonManager';
import { CameraController } from './CameraController';
import config from '@/config/config.json';
import type { ClickObject } from '@/types/cesium/ClickObject';
import type { WarningList } from '@/types/common/WarningList';
// 导出 ClearType 类型
export type { ClearType };
/**
* Cesium 工具类(重构版 - 委托模式)
*/
export class CesiumUtils {
// 管理器实例
#viewerManager: CesiumViewerManager;
#entityManager: EntityManager | null = null;
#primitiveManager: PrimitiveManager | null = null;
#layerManager: LayerManager | null = null;
#geoJsonManager: GeoJsonManager | null = null;
#cameraController: CameraController | null = null;
// 颜色缓存
#colorCache = new Map<string, Color>();
// 脉冲相关状态
#pulseMap: Record<string, { pulseId: string; probability: number }> = {};
#maxPulseRadius = 30;
#pulseDuration = 5;
#pulseCircleImage: string | null = null;
constructor() {
this.#viewerManager = new CesiumViewerManager();
}
/**
* 初始化 Cesium Viewer
* @param options - Viewer 初始化选项
* @param type - 底图类型:0=影像图,1=矢量图(默认 0)
* @param tdMapToken - 天地图 Token 数组(可选)
*/
async initCesiumViewer(
options: CesiumInitOptions,
type: number = 0,
tdMapToken?: string[]
): Promise<void> {
await this.#viewerManager.initCesiumViewer(options, type, tdMapToken);
const viewer = this.#viewerManager.getViewer();
if (viewer) {
this.#entityManager = new EntityManager(viewer);
this.#primitiveManager = new PrimitiveManager(viewer);
this.#layerManager = new LayerManager(viewer);
this.#geoJsonManager = new GeoJsonManager(viewer);
this.#cameraController = new CameraController(viewer);
}
}
/**
* 设置相机高度限制
* @param minHeight - 最小高度
* @param maxHeight - 最大高度
*/
setHeightLimits(
minHeight: number = config.camera.min,
maxHeight: number = config.camera.max
) {
this.#cameraController!.setHeightLimits(minHeight, maxHeight);
}
/**
* 清除相机高度限制
*/
clearHightLimits(): void {
this.#cameraController!.clearHeightLimits();
}
/**
* 监听相机移动结束事件,并判断相机是否超出指定矩形范围
* @param range - 矩形范围[左下角经纬度,右上角经纬度]
* @param duration - 飞行持续时间(秒),默认1秒
*/
outOverView(
range: [
[number, number],
[number, number],
] = config.defauleRectangleRange as [[number, number], [number, number]],
duration: number = 1
) {
const rectangle = Rectangle.fromDegrees(...range[0], ...range[1]);
this.#cameraController!.outOverView(rectangle, duration);
}
/**
* 销毁 Cesium Viewer
*/
destroyCesiumViewer(): void {
this.#viewerManager.destroyCesiumViewer(() =>
this.clearAllResources('all')
);
}
// ===================== 实体管理 =====================
/**
* 添加 Cesium 实体
* @param entityOptions - 实体配置选项
* @returns 创建的 Entity 实例
*/
addCesiumEntity(entityOptions: EntityOptions): Entity {
this.#checkManager(this.#entityManager, 'EntityManager');
return this.#entityManager!.addCesiumEntity(entityOptions);
}
/**
* 批量添加实体
* @param entityOptionsList - 实体配置选项数组
* @returns 创建的 Entity 实例数组
*/
addCesiumEntitiesBatch(entityOptionsList: EntityOptions[]): Entity[] {
this.#checkManager(this.#entityManager, 'EntityManager');
return this.#entityManager!.addCesiumEntitiesBatch(entityOptionsList);
}
/**
* 查询实体
* @param entityId - 实体 ID
* @returns Entity 实例,不存在则返回 null
*/
getCesiumEntityById(entityId: string): Entity | null {
this.#checkManager(this.#entityManager, 'EntityManager');
return this.#entityManager!.getCesiumEntityById(entityId);
}
/**
* 删除实体
* @param entityId - 实体 ID
* @returns 是否删除成功
*/
removeCesiumEntity(entityId: string): boolean {
this.#checkManager(this.#entityManager, 'EntityManager');
return this.#entityManager!.removeCesiumEntity(entityId);
}
/**
* 批量删除实体
* @param entityIds - 实体 ID 数组
*/
batchRemoveCesiumEntities(entityIds: string[]): void {
this.#checkManager(this.#entityManager, 'EntityManager');
this.#entityManager!.batchRemoveCesiumEntities(entityIds);
}
/**
* 清除实体
* @param clearType - 清除类型:'default'=默认实体,'custom'=自定义实体,'all'=所有实体(默认 'custom'
*/
clearAllEntities(clearType: ClearType = 'custom'): void {
this.#checkManager(this.#entityManager, 'EntityManager');
this.#entityManager!.clearAllEntities(clearType);
}
/**
* 获取所有实体ID
* @param clearType - 类型:'default'=默认实体,'custom'=自定义实体,'all'=所有实体(默认 'all'
* @returns 实体 ID 集合
*/
getEntityIds(clearType: ClearType = 'all'): Set<string> {
this.#checkManager(this.#entityManager, 'EntityManager');
return this.#entityManager!.getEntityIds(clearType);
}
// ===================== Primitive 管理 =====================
/**
* 添加单个 Primitive
* @param primitive - Primitive 配置选项
*/
addPrimitive(primitive: PrimitiveOptions): void {
this.#checkManager(this.#primitiveManager, 'PrimitiveManager');
this.#primitiveManager!.addPrimitive(primitive);
}
/**
* 批量添加 Primitive
* - 按类型分组后批量创建,减少 scene.primitives.add 调用次数
* - 同类型的多个实例合并到一个 Primitive 或 BillboardCollection 中
* @param primitives - Primitive 配置选项数组
*/
addPrimitivesBatch(primitives: PrimitiveOptions[]): void {
this.#checkManager(this.#primitiveManager, 'PrimitiveManager');
this.#primitiveManager!.addPrimitivesBatch(primitives);
}
/**
* 查询 Primitive
* @param id - Primitive ID
* @returns Primitive 或 BillboardCollection 实例,不存在则返回 undefined
*/
getPrimitiveById(id: string): Primitive | BillboardCollection | undefined {
this.#checkManager(this.#primitiveManager, 'PrimitiveManager');
return this.#primitiveManager!.getPrimitiveById(id);
}
/**
* 删除 Primitive
* @param id - Primitive ID
* @returns 是否删除成功
*/
removePrimitiveById(id: string): boolean {
this.#checkManager(this.#primitiveManager, 'PrimitiveManager');
return this.#primitiveManager!.removePrimitiveById(id);
}
/**
* 清除 Primitive
* @param clearType - 清除类型:'default'=默认 Primitive'custom'=自定义 Primitive'all'=所有 Primitive(默认 'custom'
*/
clearAllPrimitives(clearType: ClearType = 'custom'): void {
this.#checkManager(this.#primitiveManager, 'PrimitiveManager');
this.#primitiveManager!.clearAllPrimitives(clearType);
}
/**
* 获取所有Primitive ID
* @param clearType - 类型:'default'=默认 Primitive'custom'=自定义 Primitive'all'=所有 Primitive(默认 'all'
* @returns Primitive ID 集合
*/
getPrimitiveIds(clearType: ClearType = 'all'): Set<string> {
this.#checkManager(this.#primitiveManager, 'PrimitiveManager');
return this.#primitiveManager!.getPrimitiveIds(clearType);
}
/**
* 批量切换Primitives可见性
* @param ids - Primitive ID 数组
* @param visible - 是否可见
*/
batchTogglePrimitives(ids: string[], visible: boolean): void {
this.#checkManager(this.#primitiveManager, 'PrimitiveManager');
this.#primitiveManager!.batchTogglePrimitives(ids, visible);
}
/**
* 批量显示Primitives
* @param ids - Primitive ID 数组
*/
batchShowPrimitives(ids: string[]): void {
this.#checkManager(this.#primitiveManager, 'PrimitiveManager');
this.#primitiveManager!.batchShowPrimitives(ids);
}
/**
* 批量隐藏Primitives
* @param ids - Primitive ID 数组
*/
batchHidePrimitives(ids: string[]): void {
this.#checkManager(this.#primitiveManager, 'PrimitiveManager');
this.#primitiveManager!.batchHidePrimitives(ids);
}
// ===================== 图层管理 =====================
/**
* 批量创建图层
* @param layerConfigs - 图层配置数组
* @returns 创建的 ImageryLayer 实例数组(失败的为 null)
*/
createLayersBatch(layerConfigs: LayerConfig[]): (ImageryLayer | null)[] {
this.#checkManager(this.#layerManager, 'LayerManager');
return this.#layerManager!.createLayersBatch(layerConfigs);
}
/**
* 创建图层
* @param layerConfig - 图层配置
* @returns 创建的 ImageryLayer 实例,失败则返回 null
*/
createLayer(layerConfig: LayerConfig): ImageryLayer | null {
this.#checkManager(this.#layerManager, 'LayerManager');
return this.#layerManager!.createLayer(layerConfig);
}
/**
* 查询图层
* @param key - 图层 key
* @returns ImageryLayer 实例,不存在则返回 undefined
*/
getLayerByKey(key: string): ImageryLayer | undefined {
this.#checkManager(this.#layerManager, 'LayerManager');
return this.#layerManager!.getLayerByKey(key);
}
/**
* 删除图层
* @param key - 图层 key
* @returns 是否删除成功
*/
removeLayerByKey(key: string): boolean {
this.#checkManager(this.#layerManager, 'LayerManager');
return this.#layerManager!.removeLayerByKey(key);
}
/**
* 批量删除图层
* @param layerIds - 图层 ID 数组
*/
batchRemoveLayers(layerIds: string[]): void {
this.#checkManager(this.#layerManager, 'LayerManager');
this.#layerManager!.batchRemoveLayers(layerIds);
}
/**
* 清除图层
* @param clearType - 清除类型:'default'=默认图层,'custom'=自定义图层,'all'=所有图层(默认 'custom'
*/
clearAllLayers(clearType: ClearType = 'custom'): void {
this.#checkManager(this.#layerManager, 'LayerManager');
this.#layerManager!.clearAllLayers(clearType);
}
/**
* 获取所有图层 Key
* @param clearType - 类型:'default'=默认图层,'custom'=自定义图层,'all'=所有图层(默认 'all'
* @returns 图层 Key 集合
*/
getLayerKeys(clearType: ClearType = 'all'): Set<string> {
this.#checkManager(this.#layerManager, 'LayerManager');
return this.#layerManager!.getLayerKeys(clearType);
}
// ===================== GeoJSON 图层管理 =====================
/**
* 添加 GeoJSON 图层
* @param layerId - 图层唯一标识
* @param geojsonData - GeoJSON 数据(路径、URL 或对象)
* @param options - 配置选项(样式、标签等)
* @returns Promise<DataSource> 数据源实例
*/
async addGeoJsonLayer(
layerId: string,
geojsonData: CustomizeGeoJsonDataSource,
options?: GeoJsonOptions
): Promise<DataSource> {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.addGeoJsonLayer(layerId, geojsonData, options);
}
/**
* 根据ID查询图层
* @param layerId - 图层 ID
* @returns DataSource 实例,不存在则返回 undefined
*/
getGeoJsonLayerById(layerId: string): DataSource | undefined {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.getGeoJsonLayerById(layerId);
}
/**
* 删除图层
* @param layerId - 图层 ID
* @returns 是否删除成功
*/
removeGeoJsonLayer(layerId: string): boolean {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.removeGeoJsonLayer(layerId);
}
/**
* 批量添加GeoJSON图层
* @param layerConfigs - 图层配置数组,每个元素包含 layerId、geojsonData 和 options
*/
async batchAddGeoJsonLayers(
layerConfigs: Array<{
layerId: string;
geojsonData: CustomizeGeoJsonDataSource;
options?: GeoJsonOptions;
}>
): Promise<void> {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
await this.#geoJsonManager!.batchAddGeoJsonLayers(layerConfigs);
}
/**
* 批量删除
* @param layerIds - 图层 ID 数组
*/
batchRemoveGeoJsonLayers(layerIds: string[]): void {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
this.#geoJsonManager!.batchRemoveGeoJsonLayers(layerIds);
}
/**
* 清空图层
* @param clearType - 清除类型:'default'=默认图层,'custom'=自定义图层,'all'=所有图层(默认 'custom'
*/
clearAllGeoJsonLayers(clearType: ClearType = 'custom'): void {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
this.#geoJsonManager!.clearAllGeoJsonLayers(clearType);
}
/**
* 显示图层
* @param layerId - 图层 ID
* @returns 是否操作成功
*/
showGeoJsonLayer(layerId: string): boolean {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.showGeoJsonLayer(layerId);
}
/**
* 隐藏图层
* @param layerId - 图层 ID
* @returns 是否操作成功
*/
hideGeoJsonLayer(layerId: string): boolean {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.hideGeoJsonLayer(layerId);
}
/**
* 切换显隐
* @param layerId - 图层 ID
* @returns 切换后的显示状态,图层不存在则返回 null
*/
toggleGeoJsonLayer(layerId: string): boolean | null {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.toggleGeoJsonLayer(layerId);
}
/**
* 批量显示
* @param layerIds - 图层 ID 数组
* @returns 成功显示的图层数量
*/
batchShowGeoJsonLayers(layerIds: string[]): number {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.batchShowGeoJsonLayers(layerIds);
}
/**
* 批量隐藏
* @param layerIds - 图层 ID 数组
* @returns 成功隐藏的图层数量
*/
batchHideGeoJsonLayers(layerIds: string[]): number {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.batchHideGeoJsonLayers(layerIds);
}
/**
* 获取显示状态
* @param layerId - 图层 ID
* @returns 显示状态,图层不存在则返回 null
*/
getGeoJsonLayerVisibility(layerId: string): boolean | null {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.getGeoJsonLayerVisibility(layerId);
}
/**
* 获取所有 GeoJSON 图层 ID
* @param clearType - 类型:'default'=默认图层,'custom'=自定义图层,'all'=所有图层(默认 'all'
* @returns GeoJSON 图层 ID 集合
*/
getGeoJsonLayerIds(clearType: ClearType = 'all'): Set<string> {
this.#checkManager(this.#geoJsonManager, 'GeoJsonManager');
return this.#geoJsonManager!.getGeoJsonLayerIds(clearType);
}
// ===================== 图层操作 =====================
/**
* 监听点击事件
*/
clickLayer(callback: (pickedObject: ClickObject) => void) {
const handler = new ScreenSpaceEventHandler(this.getViewer()?.scene.canvas);
handler.setInputAction((clickEvent: { position: Cartesian2 }) => {
// 在点击位置进行拾取
const pickedObject = CesiumUtilsSingleton.getViewer()?.scene.pick(
clickEvent.position
);
callback(pickedObject);
}, ScreenSpaceEventType.LEFT_CLICK);
}
// ===================== 视角控制 =====================
/**
* 飞行到目标位置
* @param target - 目标位置 [经度, 纬度, 高度] 或 Cartesian3
* @param duration - 飞行持续时间(秒,默认 2)
*/
flyToTarget(
target: [number, number, number] | Cartesian3,
duration = 2
): void {
this.#checkManager(this.#cameraController, 'CameraController');
this.#cameraController!.flyToTarget(target, duration);
}
/**
* 跳转到目标位置
* @param target - 目标位置 [经度, 纬度, 高度] 或 Cartesian3
*/
viewToTarget(target: [number, number, number] | Cartesian3): void {
this.#checkManager(this.#cameraController, 'CameraController');
this.#cameraController!.viewToTarget(target);
}
// ===================== 清除与资源管理 =====================
/**
* 清除所有资源
* @param clearType - 清除类型:'default'=默认资源,'custom'=自定义资源,'all'=所有资源(默认 'custom'
*/
clearAllResources(clearType: ClearType = 'custom'): void {
this.clearAllEntities(clearType);
this.clearAllPrimitives(clearType);
this.clearAllLayers(clearType);
this.clearAllGeoJsonLayers(clearType);
}
// ===================== getter 和 setter函数 =====================
/**
* 获取 Viewer 实例
* @returns Viewer 实例,如果未初始化则返回 null
*/
getViewer(): Viewer | null {
return this.#viewerManager.getViewer();
}
// ===================== 辅助函数 =====================
/**
* 转换位置坐标
* @param pos - 位置坐标
* @returns Cartesian3 坐标
*/
convertPosition(pos: Cartesian3 | [number, number, number]): Cartesian3 {
return Array.isArray(pos)
? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0)
: pos;
}
/**
* 批量转换位置坐标
* @param positions - 位置坐标数组
* @returns Cartesian3 坐标数组
*/
convertPositionArray(
positions: (Cartesian3 | [number, number, number])[]
): Cartesian3[] {
return positions.map((pos) => this.convertPosition(pos));
}
/**
* 将Cartesian3坐标转换为屏幕坐标
* @param pos 坐标
* @returns 偏移量
*/
convertScreenPosition(pos: Cartesian3): { x: number; y: number } {
const windowCoord = SceneTransforms.wgs84ToWindowCoordinates(
this.getViewer()!.scene,
pos
);
return { x: windowCoord.x, y: windowCoord.y };
}
/**
* 解析颜色字符串
* @param colorStr 颜色字符串
* @returns Color 对象
*/
parseColor(colorStr: string): Color | null {
if (this.#colorCache.has(colorStr)) {
return this.#colorCache.get(colorStr)!;
}
const match = colorStr.match(
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/
);
if (!match) return null;
const r = parseInt(match[1]) / 255;
const g = parseInt(match[2]) / 255;
const b = parseInt(match[3]) / 255;
const a = match[4] ? parseFloat(match[4]) : 1.0;
const color = new Color(r, g, b, a);
this.#colorCache.set(colorStr, color);
return color;
}
/**
* 添加脉冲效果
* @param list 点列表
* @returns
*/
addPulseEffect(list: Record<string, WarningList>) {
const viewer = this.getViewer();
if (!viewer) return;
// 移除已有脉冲
this.#removeAllPulses();
// 生成圆形贴图
if (!this.#pulseCircleImage) {
this.#pulseCircleImage = this.#createCircleImage(this.#maxPulseRadius);
}
for (const [disasterType, warning] of Object.entries(list)) {
const { lon, lat, probability } = warning;
// 根据概率确定颜色
let color: Color;
if (probability >= 0.7) {
color = Color.RED;
} else if (probability >= 0.5) {
color = Color.YELLOW;
} else {
continue;
}
const key = `${disasterType}_${lon}_${lat}`;
const pulseId = `PULSE_${key}_${Date.now()}`;
this.#createPulseCircle(pulseId, lon, lat, color);
this.#pulseMap[key] = { pulseId, probability };
}
}
// ===================== 私有方法 =====================
/**
* 检查管理器是否已初始化
* @param manager - 管理器实例
* @param managerName - 管理器名称(用于错误提示)
*/
#checkManager(manager: unknown, managerName: string): void {
if (!manager) {
throw new Error(`${managerName} 未初始化,请先调用 initCesiumViewer()`);
}
}
/**
* 生成脉冲圆形贴图
*/
#createCircleImage(maxRadius: number): string {
const canvas = document.createElement('canvas');
canvas.width = maxRadius * 2;
canvas.height = maxRadius * 2;
const ctx = canvas.getContext('2d')!;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(maxRadius, maxRadius, maxRadius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fillStyle = 'rgba(255,255,255,0.7)';
ctx.fill();
return canvas.toDataURL('image/png');
}
/**
* 创建脉冲实体
* @param pulseId - 脉冲实体 ID
* @param lon - 经度
* @param lat - 纬度
* @param color - 颜色
*/
#createPulseCircle(
pulseId: string,
lon: number,
lat: number,
color: Color
): void {
const viewer = this.getViewer();
if (!viewer || !this.#pulseCircleImage) return;
const startTime = JulianDate.now();
const maxRadius = this.#maxPulseRadius;
const duration = this.#pulseDuration;
viewer.entities.add({
id: pulseId,
position: Cartesian3.fromDegrees(lon, lat),
billboard: {
image: this.#pulseCircleImage,
width: new CallbackProperty((time) => {
const elapsed =
JulianDate.secondsDifference(time, startTime) % duration;
const progress = elapsed / duration;
return maxRadius * 2 * Math.abs(Math.sin(progress * Math.PI));
}, false),
height: new CallbackProperty((time) => {
const elapsed =
JulianDate.secondsDifference(time, startTime) % duration;
const progress = elapsed / duration;
return maxRadius * 2 * Math.abs(Math.sin(progress * Math.PI));
}, false),
color: new CallbackProperty((time) => {
const elapsed =
JulianDate.secondsDifference(time, startTime) % duration;
const progress = elapsed / duration;
const alpha = 0.7 * (1 - progress);
return color.withAlpha(alpha);
}, false),
heightReference: HeightReference.CLAMP_TO_GROUND,
verticalOrigin: VerticalOrigin.CENTER,
horizontalOrigin: HorizontalOrigin.CENTER,
eyeOffset: new Cartesian3(0.0, 0.0, -10.0),
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
});
}
/**
* 移除所有脉冲实体
*/
#removeAllPulses(): void {
for (const key of Object.keys(this.#pulseMap)) {
this.#deletePulseEntity(key);
}
}
/**
* 删除单个脉冲实体
* @param key - 脉冲映射 key
*/
#deletePulseEntity(key: string): void {
const viewer = this.getViewer();
if (!viewer) return;
const entry = this.#pulseMap[key];
if (!entry) return;
const entity = viewer.entities.getById(entry.pulseId);
if (entity) {
viewer.entities.remove(entity);
}
delete this.#pulseMap[key];
}
}
/**
* CesiumUtils 单例实例
*/
export const CesiumUtilsSingleton = new CesiumUtils();