/** * 三维漫游管理器 * 负责加载 CZML 飞行路径、控制动画、第一人称相机跟随 */ import { czml_huanghe } from './data/riverline'; import * as Cesium from 'cesium'; export class ThreeDRoamManager { private viewer: any; private dataSource: any = null; private roamingEntity: any = null; private startTime: any = null; private callbackEvent: (() => void) | null = null; private tickCallback: ((clock: any) => void) | null = null; private totalDuration = 0; private onProgress: ((seconds: number) => void) | null = null; private positionList: number[] = []; // 开关状态 private _flightPathVisible = true; private _flightModelVisible = true; // 相机平滑跟随插值状态 private _smoothCenter: Cesium.Cartesian3 | null = null; private _smoothOrientation: Cesium.Quaternion | null = null; constructor(viewer: any) { this.viewer = viewer; } /** * 初始化漫游(加载 CZML 并定位到第0帧) * @param positionList API 返回的坐标点位数组 * @param totalTime 总时长(秒) * @param milestoneList 里程碑列表 [[name, time], ...] * @param onProgress 进度回调 */ async init( positionList: number[], totalTime: number, milestoneList?: [string, string][], onProgress?: (seconds: number) => void ): Promise { this.onProgress = onProgress || null; this.totalDuration = Math.ceil(totalTime || 0); // 0. 清除旧数据源(避免堆积) if (this.dataSource) { this.viewer.dataSources.remove(this.dataSource); this.dataSource = null; } this.roamingEntity = null; // 1. 处理点位数据(每4个点 +600 形成站点停留) const processedList = [...positionList]; for (let i = 0; i < processedList.length; i++) { if ((i + 1) % 4 === 0) { processedList[i] = processedList[i] + 600; } } this.positionList = processedList; // 2. 基于模板生成 CZML,替换坐标 const c_czml = JSON.parse(JSON.stringify(czml_huanghe)); c_czml[1].position.cartographicDegrees = processedList; // 动态更新时间区间(总时长秒 → ISO 时间) const totalSec = Math.ceil(totalTime || 0); const endDate = new Date('2021-03-16T10:00:01Z'); endDate.setSeconds(endDate.getSeconds() + totalSec); const endStr = endDate.toISOString().replace(/\.\d+Z$/, 'Z'); c_czml[0].clock.interval = `2021-03-16T10:00:01Z/${endStr}`; c_czml[0].clock.currentTime = '2021-03-16T10:00:01Z'; c_czml[1].availability = `2021-03-16T10:00:01Z/${endStr}`; // 3. 加载 CZML try { const dataSourcePromise = await this.viewer.dataSources.add( Cesium.CzmlDataSource.load(c_czml) ); this.dataSource = dataSourcePromise; this.roamingEntity = this.dataSource.entities.getById(c_czml[1].id); if (this.roamingEntity) { this.roamingEntity.path.show = this._flightPathVisible; this.roamingEntity.model.show = this._flightModelVisible; } // 4. 锁定相机到飞机模型后方(第0帧视角) this.viewer.trackedEntity = this.roamingEntity; this._smoothCenter = null; this._smoothOrientation = null; this.firstPersonModel(); this.startTime = this.viewer.clock.currentTime.clone(); this.viewer.clock.clockRange = Cesium.ClockRange.CLAMPED; this.viewer.clock.multiplier = 1; } catch (error) { console.error('CZML 加载失败:', error); } } /** 开始飞行 */ start(): void { if (!this.roamingEntity) { console.warn('请先初始化漫游数据'); return; } this.viewer.trackedEntity = this.roamingEntity; this.viewer.clock.shouldAnimate = true; this.entitiesVisible(false); this.registerTickCallback(); } /** 暂停飞行(相机仍锁定在模型位置) */ pause(): void { this.viewer.clock.shouldAnimate = false; this.entitiesVisible(true); this.removeTickCallback(); } /** 重置飞行(回到起点/第0帧) */ reset(): void { this.unLockViewAndEntity(); this.clearResult(); this.viewer.clock.shouldAnimate = false; this._smoothCenter = null; this._smoothOrientation = null; if (this.onProgress) this.onProgress(0); } /** 清除飞行路径和模型(保留相机视角完全不变) */ clear(): void { // 隐藏模型和路径(不删数据源,不动 trackedEntity 和 lookAtTransform) if (this.roamingEntity) { this.roamingEntity.path.show = false; this.roamingEntity.model.show = false; } // 停止时钟(不动相机,视角完全不变) this.viewer.clock.shouldAnimate = false; // 清理引用 this.roamingEntity = null; this.removeTickCallback(); this._smoothCenter = null; this._smoothOrientation = null; if (this.onProgress) this.onProgress(0); } /** 设置飞行倍率 */ setSpeed(multiplier: number): void { this.viewer.clock.multiplier = multiplier; } /** 切换飞行路径显隐 */ setFlightPathVisible(visible: boolean): void { this._flightPathVisible = visible; if (this.roamingEntity) { this.roamingEntity.path.show = visible; } } /** 切换飞行模型显隐 */ setFlightModelVisible(visible: boolean): void { this._flightModelVisible = visible; if (this.roamingEntity) { this.roamingEntity.model.show = visible; } } /** 跳转到指定时间点(秒) */ jumpToTime(seconds: number): void { if (this.startTime) { this.viewer.clock.currentTime = Cesium.JulianDate.addSeconds( this.startTime, seconds, new Cesium.JulianDate() ); } } /** 视角定位到指定时间点的位置 */ flyToPositionAtTime(seconds: number): void { if (!this.roamingEntity || !this.startTime) return; const time = Cesium.JulianDate.addSeconds( this.startTime, seconds, new Cesium.JulianDate() ); const position = this.roamingEntity.position?.getValue(time); if (position) { this.viewer.camera.flyTo({ destination: position, duration: 0.5 }); } } /** 是否已加载漫游实体 */ get hasEntity(): boolean { return this.roamingEntity !== null && this.roamingEntity !== undefined; } /** 销毁清理(完全释放) */ destroy(skipCesiumCleanup = false): void { if (skipCesiumCleanup) { // dim 切换:只清引用,Cesium 数据由地图切换自行销毁 this.removeTickCallback(); this.roamingEntity = null; this.dataSource = null; this.viewer = null; return; } this.clear(); if (this.dataSource) { this.viewer.dataSources.remove(this.dataSource); this.dataSource = null; } this.removeFirstPersonModel(); this.viewer = null; } // ==================== Private ==================== /** 隐藏/显示地图实体 */ private entitiesVisible(visible: boolean): void { const entities = this.viewer.entities?.values; if (entities?.length > 0) { entities.forEach((it: any) => { const name = it?.myname; if ( (name === 'tempMarketImg' || name === 'tempMarkets' || name === 'tempMarket') && it.show !== visible ) { it.show = visible; } }); } } /** 第一人称相机跟随(preRender)—— 平滑插值版 */ private firstPersonModel(): void { if (this.viewer.scene) { this.removeFirstPersonModel(); this.callbackEvent = this.viewer.scene.preRender.addEventListener(() => { this.setCameraLookAt(); }); } } /** 移除第一人称 */ private removeFirstPersonModel(): void { if (this.callbackEvent && typeof this.callbackEvent === 'function') { this.callbackEvent(); this.callbackEvent = null; } } /** 每帧平滑插值相机到模型后方(解决转向时突然跳动) */ private setCameraLookAt(): void { try { const entity = this.viewer.trackedEntity; if (!entity) return; const time = this.viewer.clock.currentTime; const targetCenter = entity.position?.getValue(time); const targetOrientation = entity.orientation?.getValue(time); if (!targetCenter || !targetOrientation) return; // 插值系数:越小越平滑(跟随延迟越大) const lerpFactor = 0.06; // 对位置做 lerp let center: Cesium.Cartesian3; if (this._smoothCenter) { center = Cesium.Cartesian3.lerp( this._smoothCenter, targetCenter, lerpFactor, new Cesium.Cartesian3() ); } else { center = targetCenter.clone(); } // 对朝向做 slerp(球面线性插值,保证旋转平滑) let orientation: Cesium.Quaternion; if (this._smoothOrientation) { orientation = Cesium.Quaternion.slerp( this._smoothOrientation, targetOrientation, lerpFactor, new Cesium.Quaternion() ); } else { orientation = targetOrientation.clone(); } this._smoothCenter = center; this._smoothOrientation = orientation; const transform = Cesium.Matrix4.fromRotationTranslation( Cesium.Matrix3.fromQuaternion(orientation), center ); this.viewer.camera.lookAtTransform( transform, new Cesium.Cartesian3(-1200, 0, 500) ); } catch (e) { // ignore render error } } /** 注册 onTick 进度回调 */ private registerTickCallback(): void { this.removeTickCallback(); const render = (clock: any) => { if (!this.viewer.clock.shouldAnimate) return; if (!this.startTime) return; const timeOffset = Math.floor( Cesium.JulianDate.secondsDifference(clock.currentTime, this.startTime) ); if (this.onProgress) { this.onProgress(timeOffset); } // 播放结束自动停止 if (this.totalDuration > 0 && timeOffset >= this.totalDuration) { this.clear(); } }; this.viewer.clock.onTick.addEventListener(render); this.tickCallback = render; } /** 移除 onTick 回调 */ private removeTickCallback(): void { if (this.tickCallback) { this.viewer.clock.onTick.removeEventListener(this.tickCallback); this.tickCallback = null; } } /** 解除实体和视角锁定 */ private unLockViewAndEntity(): void { this.viewer.trackedEntity = undefined; if (this.viewer.camera) { this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); } this.removeFirstPersonModel(); } /** 清除数据源 */ private clearResult(): void { this.viewer.clock.shouldAnimate = false; this.viewer.trackedEntity = undefined; if (this.dataSource) { this.viewer.dataSources.remove(this.dataSource); this.dataSource = null; } this.removeTickCallback(); } }