439 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
		
		
			
		
	
	
			439 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
|   | <!DOCTYPE html> | |||
|  | <html lang="zh-CN"> | |||
|  | <head> | |||
|  |     <meta charset="UTF-8"> | |||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
|  |     <title>Cesium 多资源整合加载平台</title> | |||
|  |     <!-- 引入Cesium库(使用1.116稳定版本) --> | |||
|  |     <script src="https://cesium.com/downloads/cesiumjs/releases/1.116/Build/Cesium/Cesium.js"></script> | |||
|  |     <link href="https://cesium.com/downloads/cesiumjs/releases/1.116/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> | |||
|  |     <style> | |||
|  |         /* 基础样式重置 */ | |||
|  |         html, body, #cesiumContainer { | |||
|  |             width: 100%; | |||
|  |             height: 100%; | |||
|  |             margin: 0; | |||
|  |             padding: 0; | |||
|  |             overflow: hidden; | |||
|  |         } | |||
|  | 
 | |||
|  |         /* 控制面板样式 */ | |||
|  |         .control-panel { | |||
|  |             position: absolute; | |||
|  |             top: 20px; | |||
|  |             left: 20px; | |||
|  |             background: rgba(255, 255, 255, 0.95); | |||
|  |             padding: 20px; | |||
|  |             border-radius: 8px; | |||
|  |             box-shadow: 0 3px 15px rgba(0, 0, 0, 0.15); | |||
|  |             z-index: 100; | |||
|  |             width: 420px; | |||
|  |         } | |||
|  | 
 | |||
|  |         /* 表单标题 */ | |||
|  |         .panel-title { | |||
|  |             margin: 0 0 15px; | |||
|  |             font-size: 18px; | |||
|  |             color: #2c3e50; | |||
|  |             border-bottom: 1px solid #eee; | |||
|  |             padding-bottom: 8px; | |||
|  |         } | |||
|  | 
 | |||
|  |         /* 表单分组样式 */ | |||
|  |         .form-group { | |||
|  |             margin-bottom: 15px; | |||
|  |         } | |||
|  | 
 | |||
|  |         .form-group label { | |||
|  |             display: block; | |||
|  |             margin-bottom: 6px; | |||
|  |             font-weight: 500; | |||
|  |             color: #34495e; | |||
|  |         } | |||
|  | 
 | |||
|  |         /* 输入框、选择框样式 */ | |||
|  |         .form-control { | |||
|  |             width: 100%; | |||
|  |             padding: 10px; | |||
|  |             border: 1px solid #ddd; | |||
|  |             border-radius: 4px; | |||
|  |             font-size: 14px; | |||
|  |             box-sizing: border-box; | |||
|  |             transition: border-color 0.3s; | |||
|  |         } | |||
|  | 
 | |||
|  |         .form-control:focus { | |||
|  |             outline: none; | |||
|  |             border-color: #0070f3; | |||
|  |             box-shadow: 0 0 0 2px rgba(0, 112, 243, 0.1); | |||
|  |         } | |||
|  | 
 | |||
|  |         /* 经纬度输入行布局 */ | |||
|  |         .coord-group { | |||
|  |             display: flex; | |||
|  |             gap: 10px; | |||
|  |         } | |||
|  | 
 | |||
|  |         .coord-group .form-group { | |||
|  |             flex: 1; | |||
|  |         } | |||
|  | 
 | |||
|  |         /* 按钮样式 */ | |||
|  |         .btn-group { | |||
|  |             display: flex; | |||
|  |             gap: 10px; | |||
|  |             margin-top: 10px; | |||
|  |         } | |||
|  | 
 | |||
|  |         .btn { | |||
|  |             padding: 10px 16px; | |||
|  |             border: none; | |||
|  |             border-radius: 4px; | |||
|  |             font-size: 14px; | |||
|  |             cursor: pointer; | |||
|  |             transition: background-color 0.3s; | |||
|  |         } | |||
|  | 
 | |||
|  |         .btn-primary { | |||
|  |             background-color: #0070f3; | |||
|  |             color: white; | |||
|  |         } | |||
|  | 
 | |||
|  |         .btn-primary:hover { | |||
|  |             background-color: #0051aa; | |||
|  |         } | |||
|  | 
 | |||
|  |         .btn-locate { | |||
|  |             background-color: #22c55e; | |||
|  |             color: white; | |||
|  |         } | |||
|  | 
 | |||
|  |         .btn-locate:hover { | |||
|  |             background-color: #16a34a; | |||
|  |         } | |||
|  | 
 | |||
|  |         .btn-clear { | |||
|  |             background-color: #f37000; | |||
|  |             color: white; | |||
|  |         } | |||
|  | 
 | |||
|  |         .btn-clear:hover { | |||
|  |             background-color: #d96000; | |||
|  |         } | |||
|  | 
 | |||
|  |         /* 状态提示样式 */ | |||
|  |         .status { | |||
|  |             margin-top: 12px; | |||
|  |             padding: 10px; | |||
|  |             border-radius: 4px; | |||
|  |             font-size: 14px; | |||
|  |             display: none; /* 默认隐藏 */ | |||
|  |         } | |||
|  | 
 | |||
|  |         .status-success { | |||
|  |             background-color: #e8f5e9; | |||
|  |             color: #2e7d32; | |||
|  |             display: block; /* 成功时显示 */ | |||
|  |         } | |||
|  | 
 | |||
|  |         .status-error { | |||
|  |             background-color: #fdecea; | |||
|  |             color: #d32f2f; | |||
|  |             display: block; /* 错误时显示 */ | |||
|  |         } | |||
|  | 
 | |||
|  |         /* 鼠标经纬度提示框样式 */ | |||
|  |         .latlng-tooltip { | |||
|  |             position: absolute; | |||
|  |             background: rgba(0, 0, 0, 0.8); | |||
|  |             color: white; | |||
|  |             padding: 6px 10px; | |||
|  |             border-radius: 4px; | |||
|  |             font-size: 12px; | |||
|  |             pointer-events: none; /* 避免遮挡鼠标事件 */ | |||
|  |             z-index: 1000; /* 确保在最上层显示 */ | |||
|  |             opacity: 0; /* 默认透明 */ | |||
|  |             transition: opacity 0.2s; /* 淡入淡出效果 */ | |||
|  |             white-space: nowrap; /* 防止文本换行 */ | |||
|  |         } | |||
|  |     </style> | |||
|  | </head> | |||
|  | <body> | |||
|  | <!-- Cesium 容器 --> | |||
|  | <div id="cesiumContainer"></div> | |||
|  | 
 | |||
|  | <!-- 鼠标经纬度提示框 --> | |||
|  | <div id="latlngTooltip" class="latlng-tooltip"></div> | |||
|  | 
 | |||
|  | <!-- 控制表单面板 --> | |||
|  | <div class="control-panel"> | |||
|  |     <h3 class="panel-title">Cesium 资源加载与定位</h3> | |||
|  | 
 | |||
|  |     <!-- 基础地址输入项 --> | |||
|  |     <div class="form-group"> | |||
|  |         <label for="baseUrl">基础地址(服务器根地址)</label> | |||
|  |         <input type="text" id="baseUrl" class="form-control" | |||
|  |                placeholder="例: http://192.168.110.25:8848" | |||
|  |                value="http://192.168.110.25:8848"> | |||
|  |     </div> | |||
|  | 
 | |||
|  |     <!-- 资源地址输入 --> | |||
|  |     <div class="form-group"> | |||
|  |         <label for="resourceUrl">资源相对路径</label> | |||
|  |         <input type="text" id="resourceUrl" class="form-control" | |||
|  |                placeholder="例: 1.倾斜模型: data/clt/.../tileset.json 2.高程: terrain_pak 3.瓦片: tiles/{z}/{x}/{y}.png" | |||
|  |                value="/data/pak/8870a7a573dc621d7347457a5497df3b/{z}/{x}/{y}.png"> | |||
|  |     </div> | |||
|  | 
 | |||
|  |     <!-- 经纬度定位输入 --> | |||
|  |     <div class="coord-group"> | |||
|  |         <div class="form-group"> | |||
|  |             <label for="longitude">经度</label> | |||
|  |             <input type="text" id="longitude" class="form-control" | |||
|  |                    placeholder="范围: -180 ~ 180" value="106.253200504443"> | |||
|  |         </div> | |||
|  |         <div class="form-group"> | |||
|  |             <label for="latitude">纬度</label> | |||
|  |             <input type="text" id="latitude" class="form-control" | |||
|  |                    placeholder="范围: -90 ~ 90" value="29.8500521523625"> | |||
|  |         </div> | |||
|  |     </div> | |||
|  | 
 | |||
|  |     <!-- 操作按钮组 --> | |||
|  |     <div class="btn-group"> | |||
|  |         <button id="loadBtn" class="btn btn-primary">加载资源</button> | |||
|  |         <button id="locateBtn" class="btn btn-locate">定位到经纬度</button> | |||
|  |         <button id="clearBtn" class="btn btn-clear">清除所有资源</button> | |||
|  |     </div> | |||
|  | 
 | |||
|  |     <!-- 状态提示 --> | |||
|  |     <div id="status" class="status"></div> | |||
|  | </div> | |||
|  | 
 | |||
|  | <script> | |||
|  |     // 1. 初始化Cesium核心配置 | |||
|  |     Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5MGU0NGMwYS00ZDBkLTQzMDItYjc5Zi0zYTM1NDcwZGVjMmEiLCJpZCI6MTU1MTk5LCJpYXQiOjE2ODk4MTgzNTF9.V_eZ5KlAI9qmxsDivT6pMC3Pq6qLk0mXpBoe5C0Mm4g'; | |||
|  | 
 | |||
|  |     // 2. 创建Cesium Viewer(基础配置) | |||
|  |     const viewer = new Cesium.Viewer('cesiumContainer', { | |||
|  |         animation: false, | |||
|  |         timeline: false, | |||
|  |         vrButton: false, | |||
|  |         infoBox: false, | |||
|  |         selectionIndicator: false, | |||
|  |         homeButton: true, | |||
|  |         sceneModePicker: true, | |||
|  |         navigationHelpButton: true, | |||
|  |         fullscreenButton: true, | |||
|  |         imageryProvider: false | |||
|  |     }); | |||
|  | 
 | |||
|  |     // 隐藏Cesium版权信息 | |||
|  |     viewer._cesiumWidget._creditContainer.style.display = 'none'; | |||
|  | 
 | |||
|  |     // 3. 全局变量 | |||
|  |     let loadedResource = { type: null, instance: null }; | |||
|  |     const statusEl = document.getElementById('status'); | |||
|  |     const latlngTooltip = document.getElementById('latlngTooltip'); | |||
|  | 
 | |||
|  |     // 4. 工具函数: 拼接资源URL | |||
|  |     function getFullResourceUrl() { | |||
|  |         const baseUrl = document.getElementById('baseUrl').value.trim(); | |||
|  |         const resourceUrl = document.getElementById('resourceUrl').value.trim(); | |||
|  |         if (!resourceUrl) return ''; | |||
|  |         if (!baseUrl) return resourceUrl; | |||
|  |         const baseUrlWithSlash = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; | |||
|  |         const cleanResourceUrl = resourceUrl.startsWith('/') ? resourceUrl.slice(1) : resourceUrl; | |||
|  |         return `${baseUrlWithSlash}${cleanResourceUrl}`; | |||
|  |     } | |||
|  | 
 | |||
|  |     // 5. 工具函数: 识别资源类型 | |||
|  |     function getResourceTypeByUrl(fullUrl) { | |||
|  |         const lowerUrl = fullUrl.toLowerCase(); | |||
|  |         if (lowerUrl.includes('.png') || lowerUrl.includes('.jpg')) return 'imagery'; | |||
|  |         if (lowerUrl.includes('tileset.json')) return 'tileset'; | |||
|  |         if (lowerUrl.includes('pak')) return 'terrain'; | |||
|  |         return null; | |||
|  |     } | |||
|  | 
 | |||
|  |     // 6. 状态提示函数 | |||
|  |     function showStatus(message, isSuccess = true) { | |||
|  |         statusEl.textContent = message; | |||
|  |         statusEl.className = 'status'; | |||
|  |         statusEl.classList.add(isSuccess ? 'status-success' : 'status-error'); | |||
|  |         if (isSuccess) { | |||
|  |             setTimeout(() => { | |||
|  |                 statusEl.classList.remove('status-success'); | |||
|  |                 statusEl.style.display = 'none'; | |||
|  |             }, 5000); | |||
|  |         } | |||
|  |     } | |||
|  | 
 | |||
|  |     // 7. 清除资源函数 | |||
|  |     function clearLoadedResource() { | |||
|  |         if (!loadedResource.instance) return; | |||
|  |         switch (loadedResource.type) { | |||
|  |             case 'tileset': | |||
|  |                 viewer.scene.primitives.remove(loadedResource.instance); | |||
|  |                 showStatus('倾斜模型已清除'); | |||
|  |                 break; | |||
|  |             case 'imagery': | |||
|  |                 viewer.imageryLayers.remove(loadedResource.instance); | |||
|  |                 showStatus('二维瓦片已清除'); | |||
|  |                 break; | |||
|  |             case 'terrain': | |||
|  |                 viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider(); | |||
|  |                 viewer.scene.globe.depthTestAgainstTerrain = false; | |||
|  |                 showStatus('高程模型已清除'); | |||
|  |                 break; | |||
|  |         } | |||
|  |         loadedResource = { type: null, instance: null }; | |||
|  |     } | |||
|  | 
 | |||
|  |     // 8. 资源加载函数 | |||
|  |     async function loadResource() { | |||
|  |         const fullUrl = getFullResourceUrl(); | |||
|  |         if (!fullUrl) { | |||
|  |             showStatus('请输入有效的资源相对路径', false); | |||
|  |             return; | |||
|  |         } | |||
|  | 
 | |||
|  |         const resourceType = getResourceTypeByUrl(fullUrl); | |||
|  |         if (!resourceType) { | |||
|  |             showStatus('无法识别资源类型、请检查路径是否包含tileset.json/pak/.png/.jpg', false); | |||
|  |             return; | |||
|  |         } | |||
|  | 
 | |||
|  |         clearLoadedResource(); | |||
|  | 
 | |||
|  |         try { | |||
|  |             let instance; | |||
|  |             switch (resourceType) { | |||
|  |                 case 'tileset': | |||
|  |                     showStatus('正在加载倾斜模型...'); | |||
|  |                     instance = await Cesium.Cesium3DTileset.fromUrl(fullUrl); | |||
|  |                     viewer.scene.primitives.add(instance); | |||
|  |                     viewer.zoomTo(instance); | |||
|  |                     showStatus('倾斜模型加载成功'); | |||
|  |                     break; | |||
|  |                 case 'imagery': | |||
|  |                     showStatus('正在加载二维瓦片...'); | |||
|  |                     const fileExtension = fullUrl.toLowerCase().includes('.png') ? 'png' : 'jpg'; | |||
|  |                     const provider = new Cesium.UrlTemplateImageryProvider({ | |||
|  |                         url: fullUrl, | |||
|  |                         fileExtension: fileExtension, | |||
|  |                         minimumLevel: 0, | |||
|  |                         maximumLevel: 18, | |||
|  |                         credit: '自定义二维瓦片' | |||
|  |                     }); | |||
|  |                     instance = viewer.imageryLayers.addImageryProvider(provider); | |||
|  |                     showStatus('二维瓦片加载成功'); | |||
|  |                     break; | |||
|  |                 case 'terrain': | |||
|  |                     showStatus('正在加载高程模型...'); | |||
|  |                     instance = await Cesium.CesiumTerrainProvider.fromUrl(fullUrl); | |||
|  |                     viewer.terrainProvider = instance; | |||
|  |                     viewer.scene.globe.depthTestAgainstTerrain = true; | |||
|  |                     showStatus('高程模型加载成功'); | |||
|  |                     break; | |||
|  |             } | |||
|  |             loadedResource = { type: resourceType, instance: instance }; | |||
|  |         } catch (error) { | |||
|  |             console.error('资源加载失败: ', error); | |||
|  |             const errorMsg = error.message.includes('404') ? '资源地址不存在(404)' : | |||
|  |                 error.message.includes('CORS') ? '跨域访问被拒绝(CORS)' : | |||
|  |                     '服务器连接失败或资源格式错误'; | |||
|  |             showStatus(`资源加载失败: ${errorMsg}`, false); | |||
|  |         } | |||
|  |     } | |||
|  | 
 | |||
|  |     // 9. 经纬度定位函数 | |||
|  |     function flyToCoordinate() { | |||
|  |         const lng = parseFloat(document.getElementById('longitude').value.trim()); | |||
|  |         const lat = parseFloat(document.getElementById('latitude').value.trim()); | |||
|  |         if (isNaN(lng) || isNaN(lat) || lng < -180 || lng > 180 || lat < -90 || lat > 90) { | |||
|  |             showStatus('请输入有效的经纬度(经度: -180~180、纬度: -90~90)', false); | |||
|  |             return; | |||
|  |         } | |||
|  |         viewer.camera.flyTo({ | |||
|  |             destination: Cesium.Cartesian3.fromDegrees(lng, lat, 10000), | |||
|  |             duration: 2, | |||
|  |             orientation: { | |||
|  |                 heading: Cesium.Math.toRadians(0), | |||
|  |                 pitch: Cesium.Math.toRadians(-30), | |||
|  |                 roll: 0 | |||
|  |             } | |||
|  |         }); | |||
|  |         showStatus(`已定位到经纬度: (${lng.toFixed(4)}, ${lat.toFixed(4)})`); | |||
|  |     } | |||
|  | 
 | |||
|  |     // 10. 修复: 鼠标悬浮显示经纬度(支持地球+地形表面、避免NaN) | |||
|  |     function initLatlngTooltip() { | |||
|  |         const canvas = viewer.scene.canvas; | |||
|  | 
 | |||
|  |         // 鼠标移动事件 | |||
|  |         canvas.addEventListener('mousemove', (e) => { | |||
|  |             // 1. 先尝试获取地形表面坐标(优先、因为加载高程后更准确) | |||
|  |             const windowPosition = new Cesium.Cartesian2(e.clientX, e.clientY); | |||
|  |             let cartographic = null; | |||
|  | 
 | |||
|  |             // 方法1: 从地形表面获取坐标(适用于加载了高程模型的场景) | |||
|  |             const ray = viewer.camera.getPickRay(windowPosition); | |||
|  |             if (ray) { | |||
|  |                 const hitResult = viewer.scene.pickFromRay(ray); | |||
|  |                 if (hitResult && hitResult.position) { | |||
|  |                     cartographic = Cesium.Cartographic.fromCartesian(hitResult.position); | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             // 方法2: 如果地形获取失败、从椭球面获取(适用于无高程的场景) | |||
|  |             if (!cartographic) { | |||
|  |                 cartographic = viewer.scene.camera.pickEllipsoid(windowPosition); | |||
|  |             } | |||
|  | 
 | |||
|  |             // 3. 计算并显示经纬度(增加异常处理) | |||
|  |             if (cartographic && !isNaN(cartographic.longitude) && !isNaN(cartographic.latitude)) { | |||
|  |                 const longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(6); | |||
|  |                 const latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(6); | |||
|  |                 // 显示经纬度(避免NaN) | |||
|  |                 latlngTooltip.textContent = `经度: ${longitude} | 纬度: ${latitude}`; | |||
|  |                 // 调整提示框位置(防止超出屏幕) | |||
|  |                 const tooltipWidth = latlngTooltip.offsetWidth || 150; // 预估宽度 | |||
|  |                 const left = e.clientX + 10 > window.innerWidth - tooltipWidth | |||
|  |                     ? e.clientX - tooltipWidth - 10 | |||
|  |                     : e.clientX + 10; | |||
|  |                 const top = e.clientY + 10 > window.innerHeight - 30 | |||
|  |                     ? e.clientY - 30 | |||
|  |                     : e.clientY + 10; | |||
|  |                 latlngTooltip.style.left = `${left}px`; | |||
|  |                 latlngTooltip.style.top = `${top}px`; | |||
|  |                 latlngTooltip.style.opacity = '1'; | |||
|  |             } else { | |||
|  |                 // 非地球表面时隐藏提示框 | |||
|  |                 latlngTooltip.style.opacity = '0'; | |||
|  |             } | |||
|  |         }); | |||
|  | 
 | |||
|  |         // 鼠标离开画布时隐藏 | |||
|  |         canvas.addEventListener('mouseleave', () => { | |||
|  |             latlngTooltip.style.opacity = '0'; | |||
|  |         }); | |||
|  |     } | |||
|  | 
 | |||
|  |     // 11. 绑定按钮事件 | |||
|  |     document.getElementById('loadBtn').addEventListener('click', loadResource); | |||
|  |     document.getElementById('locateBtn').addEventListener('click', flyToCoordinate); | |||
|  |     document.getElementById('clearBtn').addEventListener('click', () => { | |||
|  |         clearLoadedResource(); | |||
|  |         if (viewer.imageryLayers.length > 0) { | |||
|  |             viewer.imageryLayers.removeAll(); | |||
|  |             showStatus('所有影像图层已清除'); | |||
|  |         } | |||
|  |     }); | |||
|  | 
 | |||
|  |     // 12. 初始化鼠标经纬度提示 | |||
|  |     initLatlngTooltip(); | |||
|  | </script> | |||
|  | </body> | |||
|  | </html> |