// 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 = ''; init(container: HTMLElement, rectangle?: any): Promise { 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 }); } // ... 其他空实现方法保持不变 ... jdPanelControlShowAndHidden(baseid: string, isAll: boolean): void {} mdLayerShowOrHidden(): void {} addBaseDataLayer(layer: any): void {} controlBaseLayerTreeShowAndHidden( layerType: string, key: string, checked: boolean ): void {} mdLayerTreeShowOrHidden(layerType: string, checked?: boolean): void {} hasLayer(layerKey: string): boolean { return false; } hasBaseLayer(layerKey: string): boolean { return false; } setLegendPointVisible( layerKey: string, anchoPointState: string, checked: boolean ): void {} addInitDataLayer(pointData: any[], layerType: any, mdoptions?: any): void {} switchView(type: any): void {} fitBounds(bounds: any): void {} baseLayerSwitcher(key: string): void {} addTertiarybasinLayer( layer: layer, fillcolor: any, outlineColor: any, datas: any ): void {} 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.' ); } } } // ... 其他代码 ... initPopupOverlay(popupContainer: HTMLDivElement): void {} }