WholeProcessPlatform/frontend/src/components/gis/map.cesium.ts

331 lines
9.8 KiB
TypeScript
Raw Normal View History

// src/components/gis/map.cesium.ts
import * as Cesium from 'cesium';
import type { MapInterface, layer } from './map.d';
// ✅ 1. 全局配置 Cesium 静态资源路径 (vite-plugin-cesium 通常会自动处理,但显式指定更保险)
// 注意:如果你使用了 vite-plugin-cesium这一步通常不需要但如果报错可以尝试取消注释下面这行
// Cesium.buildModuleUrl.setBaseUrl('/node_modules/cesium/Build/Cesium/');
export class MapCesium implements MapInterface {
private viewer: Cesium.Viewer | null = null;
private containerId = '';
2026-07-09 14:58:14 +08:00
private containerElement: HTMLElement | null = null;
private loadingOverlayElement: HTMLDivElement | null = null;
private loadingSpinnerAnimation: Animation | null = null;
private flightFallbackTimer: number | null = null;
2026-07-09 14:58:14 +08:00
private readonly CHINA_CENTER = {
lng: 114.5,
lat: 36.5
};
private readonly CHINA_BOOT_HEIGHT = 20000000;
private readonly CHINA_DEFAULT_HEIGHT = 12000000;
private readonly CHINA_VIEW_ORIENTATION = {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-90),
roll: 0
};
2026-07-09 14:58:14 +08:00
async init(container: HTMLElement, _rectangle?: any): Promise<any> {
try {
this.containerId = container.id;
this.containerElement = container;
this.showLoadingOverlay(container);
this.viewer = new Cesium.Viewer(container, {
animation: false,
timeline: false,
baseLayerPicker: false,
fullscreenButton: false,
vrButton: false,
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
infoBox: false,
selectionIndicator: false,
shouldAnimate: true
});
const layers = this.viewer.imageryLayers;
layers.removeAll();
const arcgisUrl =
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/';
const provider = await Cesium.ArcGisMapServerImageryProvider.fromUrl(
arcgisUrl,
{
enablePickFeatures: false,
maximumLevel: 19
}
);
layers.addImageryProvider(provider);
this.viewer.scene.globe.depthTestAgainstTerrain = false;
this.viewer.useBrowserRecommendedResolution = true;
// 先把相机直接摆到中国上空,避免初始化阶段短暂闪到美国默认视角。
this.setBootChinaView();
await this.waitForViewerReady();
await this.flyToChina();
return this.viewer;
} catch (error) {
this.hideLoadingOverlay();
console.error('Cesium Init Critical Error:', error);
throw error;
}
}
2026-07-09 14:58:14 +08:00
private setBootChinaView() {
if (!this.viewer) return;
this.viewer.camera.cancelFlight();
2026-07-09 14:58:14 +08:00
this.viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(
this.CHINA_CENTER.lng,
this.CHINA_CENTER.lat,
this.CHINA_BOOT_HEIGHT
),
orientation: this.CHINA_VIEW_ORIENTATION
});
}
2026-07-09 14:58:14 +08:00
private waitForViewerReady(): Promise<void> {
return new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
this.viewer?.scene.requestRender();
resolve();
});
});
});
}
2026-07-09 14:58:14 +08:00
private flyToChina(): Promise<void> {
return new Promise(resolve => {
if (!this.viewer) {
this.hideLoadingOverlay();
resolve();
return;
}
const finish = () => {
if (this.flightFallbackTimer !== null) {
window.clearTimeout(this.flightFallbackTimer);
this.flightFallbackTimer = null;
}
this.hideLoadingOverlay();
resolve();
};
this.viewer.camera.cancelFlight();
this.flightFallbackTimer = window.setTimeout(() => {
finish();
}, 2600);
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
this.CHINA_CENTER.lng,
this.CHINA_CENTER.lat,
this.CHINA_DEFAULT_HEIGHT
),
orientation: this.CHINA_VIEW_ORIENTATION,
duration: 1.8,
complete: finish,
cancel: finish
});
});
}
private showLoadingOverlay(container: HTMLElement) {
this.hideLoadingOverlay();
const overlay = document.createElement('div');
overlay.setAttribute('data-map-cesium-loading', 'true');
overlay.style.position = 'absolute';
overlay.style.inset = '0';
overlay.style.display = 'flex';
overlay.style.flexDirection = 'column';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
overlay.style.gap = '12px';
overlay.style.background =
'linear-gradient(180deg, rgba(7, 20, 36, 0.72) 0%, rgba(7, 20, 36, 0.46) 100%)';
overlay.style.backdropFilter = 'blur(2px)';
overlay.style.zIndex = '30';
overlay.style.pointerEvents = 'auto';
const spinner = document.createElement('div');
spinner.style.width = '36px';
spinner.style.height = '36px';
spinner.style.borderRadius = '50%';
spinner.style.border = '3px solid rgba(255, 255, 255, 0.25)';
spinner.style.borderTopColor = '#ffffff';
this.loadingSpinnerAnimation = spinner.animate(
[{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }],
{
duration: 900,
iterations: Infinity,
easing: 'linear'
}
);
const label = document.createElement('div');
label.textContent = '3D场景加载中...';
label.style.color = '#ffffff';
label.style.fontSize = '14px';
label.style.lineHeight = '20px';
label.style.letterSpacing = '0.5px';
label.style.textShadow = '0 1px 2px rgba(0, 0, 0, 0.35)';
overlay.appendChild(spinner);
overlay.appendChild(label);
container.appendChild(overlay);
this.loadingOverlayElement = overlay;
}
private hideLoadingOverlay() {
if (this.loadingSpinnerAnimation) {
this.loadingSpinnerAnimation.cancel();
this.loadingSpinnerAnimation = null;
}
if (this.loadingOverlayElement?.parentNode) {
this.loadingOverlayElement.parentNode.removeChild(
this.loadingOverlayElement
);
}
this.loadingOverlayElement = null;
}
destroy(): void {
2026-07-09 14:58:14 +08:00
if (this.flightFallbackTimer !== null) {
window.clearTimeout(this.flightFallbackTimer);
this.flightFallbackTimer = null;
}
this.hideLoadingOverlay();
if (this.viewer) {
this.viewer.camera.cancelFlight();
this.viewer.destroy();
this.viewer = null;
}
2026-07-09 14:58:14 +08:00
this.containerElement = null;
}
flyTopanto(position: number[], zoom: number): void {
if (!this.viewer || !position) return;
const [lng, lat] = position;
const height = 20000000 / Math.pow(2, zoom);
this.viewer.camera.cancelFlight();
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
lng,
lat,
height > 100 ? height : 1000
),
duration: 1.5
});
}
// ... 其他空实现方法保持不变 ...
2026-07-09 14:58:14 +08:00
jdPanelControlShowAndHidden(_baseid: string, _isAll: boolean): void {}
mdLayerShowOrHidden(): void {}
2026-07-09 14:58:14 +08:00
addBaseDataLayer(_layer: any): void {}
controlBaseLayerTreeShowAndHidden(
2026-07-09 14:58:14 +08:00
_layerType: string,
_key: string,
_checked: boolean
): void {}
2026-07-09 14:58:14 +08:00
mdLayerTreeShowOrHidden(_layerType: string, _checked?: boolean): void {}
hasLayer(_layerKey: string): boolean {
2026-07-01 08:48:39 +08:00
return false;
}
2026-07-09 14:58:14 +08:00
hasBaseLayer(_layerKey: string): boolean {
2026-07-01 08:48:39 +08:00
return false;
}
setLegendPointVisible(
2026-07-09 14:58:14 +08:00
_layerKey: string,
_anchoPointState: string,
_checked: boolean
): void {}
2026-07-09 14:58:14 +08:00
addInitDataLayer(
_pointData: any[],
_layerType: any,
_mdoptions?: any
): void {}
switchView(_type: any): void {}
fitBounds(_bounds: any): void {}
baseLayerSwitcher(_key: string): void {}
addTertiarybasinLayer(
2026-07-09 14:58:14 +08:00
_layer: layer,
_fillcolor: any,
_outlineColor: any,
_datas: any
): void {}
2026-07-09 14:58:14 +08:00
hideTertiarybasinLayer(_layer: layer): void {}
zoomToggle(type: 'out' | 'in'): void {
if (!this.viewer) return;
const factor = 1.5;
if (type === 'in') this.viewer.camera.zoomIn(factor);
else this.viewer.camera.zoomOut(factor);
}
lengthCalculate(): void {}
areCalculate(): void {}
removeQueryLayer(): void {}
// src/components/gis/map.cesium.ts
/**
*
* @param fileName 'cesium_map.png'
*/
mapOutPut(fileName = 'cesium_map.png'): void {
if (!this.viewer) {
console.warn('Viewer is not initialized.');
return;
}
try {
const canvas = this.viewer.canvas;
// ✅ 关键步骤 1: 强制渲染一帧,确保画布内容是最新的
// 这有助于解决因缓冲区清除导致的黑屏问题
this.viewer.render();
// ✅ 关键步骤 2: 尝试获取数据
// 注意:如果存在跨域污染,这里可能会抛出 SecurityError
const dataURL = canvas.toDataURL('image/png');
// 检查是否获取到了有效数据(简单的长度检查)
if (dataURL.length < 100) {
console.warn(
'Canvas data is too small, likely black or empty. Check CORS settings.'
);
// 可以尝试 fallback 到 blob 方式,但通常 CORS 问题两者都会失败
}
// 创建临时链接元素
const link = document.createElement('a');
link.href = dataURL;
link.download = fileName;
// 触发点击事件进行下载
document.body.appendChild(link);
link.click();
// 清理临时元素
document.body.removeChild(link);
} catch (error) {
console.error('Failed to export map image:', error);
if (error instanceof DOMException && error.name === 'SecurityError') {
console.error(
'Security Error: The canvas has been tainted by cross-origin data. Ensure all imagery providers support CORS.'
);
}
}
}
// ... 其他代码 ...
2026-07-09 14:58:14 +08:00
initPopupOverlay(_popupContainer: HTMLDivElement): void {}
}