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(); // 脉冲相关状态 #pulseMap: Record = {}; #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 { 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 { 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 { 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 { this.#checkManager(this.#layerManager, 'LayerManager'); return this.#layerManager!.getLayerKeys(clearType); } // ===================== GeoJSON 图层管理 ===================== /** * 添加 GeoJSON 图层 * @param layerId - 图层唯一标识 * @param geojsonData - GeoJSON 数据(路径、URL 或对象) * @param options - 配置选项(样式、标签等) * @returns Promise 数据源实例 */ async addGeoJsonLayer( layerId: string, geojsonData: CustomizeGeoJsonDataSource, options?: GeoJsonOptions ): Promise { 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 { 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 { 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) { 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();