WholeProcessPlatform/frontend/src/components/gis/roam/ThreeDRoamManager.ts

363 lines
11 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.

/**
* 三维漫游管理器
* 负责加载 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<void> {
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();
}
}