2026-05-15 17:38:03 +08:00
|
|
|
|
// 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;
|
2026-06-01 08:42:06 +08:00
|
|
|
|
private containerId = '';
|
2026-05-15 17:38:03 +08:00
|
|
|
|
|
|
|
|
|
|
init(container: HTMLElement, rectangle?: any): Promise<any> {
|
|
|
|
|
|
return new Promise(async (resolve, reject) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.containerId = container.id;
|
|
|
|
|
|
// ✅ 2. 初始化 Viewer
|
|
|
|
|
|
// 在 1.141.0 中,建议先不传 imageryProvider,或者使用默认的 Ion Imagery
|
|
|
|
|
|
// 如果不想用 Ion,可以传一个空的 ImageryProviderCollection 或者稍后移除
|
|
|
|
|
|
this.viewer = new Cesium.Viewer(this.containerId, {
|
|
|
|
|
|
animation: false, // 隐藏动画
|
|
|
|
|
|
timeline: false, // 隐藏时间线
|
|
|
|
|
|
baseLayerPicker: false, // 隐藏底图选择器
|
|
|
|
|
|
fullscreenButton: false, // 隐藏全屏按钮
|
|
|
|
|
|
vrButton: false, // 隐藏VR按钮
|
|
|
|
|
|
geocoder: false, // 隐藏地址搜索框
|
|
|
|
|
|
homeButton: false, // 隐藏首页按钮
|
|
|
|
|
|
sceneModePicker: false, // 隐藏场景模式选择器
|
|
|
|
|
|
navigationHelpButton: false, // 隐藏导航帮助按钮
|
|
|
|
|
|
infoBox: false, // 隐藏信息框
|
|
|
|
|
|
selectionIndicator: false, // 隐藏选择指示器
|
|
|
|
|
|
shouldAnimate: true // 开启动画
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// // 清空默认图层
|
|
|
|
|
|
const layers = this.viewer.imageryLayers;
|
|
|
|
|
|
layers.removeAll();
|
|
|
|
|
|
// // 添加 ArcGIS 底图(正确 URL)
|
|
|
|
|
|
const arcgisUrl =
|
|
|
|
|
|
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/';
|
|
|
|
|
|
|
|
|
|
|
|
const provider = await Cesium.ArcGisMapServerImageryProvider.fromUrl(
|
|
|
|
|
|
arcgisUrl,
|
|
|
|
|
|
{
|
|
|
|
|
|
enablePickFeatures: false,
|
|
|
|
|
|
// 可选:设置最大层级,避免请求过细的瓦片
|
|
|
|
|
|
maximumLevel: 19
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
// ✅ 6. 【关键】将 Provider 添加到图层集合中
|
|
|
|
|
|
layers.addImageryProvider(provider);
|
|
|
|
|
|
|
|
|
|
|
|
// layers.addImageryProvider(provider);
|
|
|
|
|
|
|
|
|
|
|
|
// // ✅ 5. 场景优化
|
|
|
|
|
|
this.viewer.scene.globe.depthTestAgainstTerrain = false;
|
|
|
|
|
|
// // 启用高 DPI 支持(可选,根据性能需求)
|
|
|
|
|
|
this.viewer.useBrowserRecommendedResolution = true;
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 6. 飞行到中国视角
|
|
|
|
|
|
// 使用 setTimeout 确保 DOM 和 WebGL 上下文完全就绪
|
|
|
|
|
|
// setTimeout(() => {
|
|
|
|
|
|
// this.flyToChina();
|
|
|
|
|
|
// resolve(this.viewer);
|
|
|
|
|
|
// }, 100);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Cesium Init Critical Error:', error);
|
|
|
|
|
|
reject(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private flyToChina() {
|
|
|
|
|
|
if (!this.viewer) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 取消之前的飞行动画(如果有)
|
|
|
|
|
|
this.viewer.camera.cancelFlight();
|
|
|
|
|
|
|
|
|
|
|
|
this.viewer.camera.flyTo({
|
|
|
|
|
|
destination: Cesium.Cartesian3.fromDegrees(104.5, 36.5, 4000000),
|
|
|
|
|
|
orientation: {
|
|
|
|
|
|
heading: Cesium.Math.toRadians(0),
|
|
|
|
|
|
pitch: Cesium.Math.toRadians(-90),
|
|
|
|
|
|
roll: 0.0
|
|
|
|
|
|
},
|
|
|
|
|
|
duration: 2
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
destroy(): void {
|
|
|
|
|
|
if (this.viewer) {
|
|
|
|
|
|
// ✅ 关键:销毁前取消所有动画和事件
|
|
|
|
|
|
this.viewer.camera.cancelFlight();
|
|
|
|
|
|
this.viewer.destroy();
|
|
|
|
|
|
this.viewer = 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-06-01 08:42:06 +08:00
|
|
|
|
jdPanelControlShowAndHidden(baseid: string, isAll: boolean): void {}
|
2026-05-15 17:38:03 +08:00
|
|
|
|
mdLayerShowOrHidden(): void {}
|
|
|
|
|
|
addBaseDataLayer(layer: any): void {}
|
|
|
|
|
|
controlBaseLayerTreeShowAndHidden(
|
2026-06-01 08:42:06 +08:00
|
|
|
|
layerType: string,
|
|
|
|
|
|
key: string,
|
2026-05-15 17:38:03 +08:00
|
|
|
|
checked: boolean
|
|
|
|
|
|
): void {}
|
2026-06-01 08:42:06 +08:00
|
|
|
|
mdLayerTreeShowOrHidden(layerType: string, checked?: boolean): void {}
|
2026-07-01 08:48:39 +08:00
|
|
|
|
hasLayer(layerKey: string): boolean {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
hasBaseLayer(layerKey: string): boolean {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-06-03 15:03:31 +08:00
|
|
|
|
setLegendPointVisible(
|
|
|
|
|
|
layerKey: string,
|
|
|
|
|
|
anchoPointState: string,
|
|
|
|
|
|
checked: boolean
|
|
|
|
|
|
): void {}
|
2026-05-15 17:38:03 +08:00
|
|
|
|
addInitDataLayer(pointData: any[], layerType: any, mdoptions?: any): void {}
|
2026-06-01 08:42:06 +08:00
|
|
|
|
switchView(type: any): void {}
|
|
|
|
|
|
fitBounds(bounds: any): void {}
|
2026-05-15 17:38:03 +08:00
|
|
|
|
baseLayerSwitcher(key: string): void {}
|
|
|
|
|
|
addTertiarybasinLayer(
|
|
|
|
|
|
layer: layer,
|
|
|
|
|
|
fillcolor: any,
|
|
|
|
|
|
outlineColor: any,
|
|
|
|
|
|
datas: any
|
|
|
|
|
|
): void {}
|
2026-06-03 15:03:31 +08:00
|
|
|
|
hideTertiarybasinLayer(layer: layer): void {}
|
2026-05-15 17:38:03 +08:00
|
|
|
|
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'
|
|
|
|
|
|
*/
|
2026-06-01 08:42:06 +08:00
|
|
|
|
mapOutPut(fileName = 'cesium_map.png'): void {
|
2026-05-15 17:38:03 +08:00
|
|
|
|
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.'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ... 其他代码 ...
|
|
|
|
|
|
initPopupOverlay(popupContainer: HTMLDivElement): void {}
|
|
|
|
|
|
}
|