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

371 lines
12 KiB
TypeScript
Raw Normal View History

/**
*
* 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;
// 点击跳转后的过渡截止时间performance.now() 时间戳):
// 过渡期内 setCameraLookAt 使用更大的插值系数,让相机平滑“飞”向目标而非瞬移
private _transitionEnd = 0;
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()
);
}
}
/**
*
* 使 camera.flyTo setCameraLookAt
* lookAtTransformcamera.flyTo /zoom
*
* setCameraLookAt
* / setCameraLookAt
*/
flyToPositionAtTime(seconds: number): void {
if (!this.roamingEntity || !this.startTime) return;
this._transitionEnd = performance.now() + 500;
}
/** 是否已加载漫游实体 */
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;
// 插值系数:常规跟随 0.1(越小越平滑,跟随延迟越大);
// 点击跳转后的过渡期内 0.15 → 0.1 线性衰减,让相机快速且平滑地“飞”向目标位置
const now = performance.now();
let lerpFactor = 0.1;
if (now < this._transitionEnd) {
const t = 1 - (this._transitionEnd - now) / 500; // 0 → 1
lerpFactor = 0.15 + (0.1 - 0.15) * t;
}
// 对位置做 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();
}
}