<!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> <link href="libs/Widgets/widgets.css" rel="stylesheet"> <script src="libs/Cesium.js"></script> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } #statusMsg { position: absolute; bottom: 20px; left: 20px; background: rgba(0, 0, 0, 0.6); color: white; padding: 8px 16px; border-radius: 4px; font-family: sans-serif; font-size: 14px; z-index: 100; pointer-events: none; } /* 气泡标注样式 - 简洁蓝色 */ #bubble { position: absolute; background: linear-gradient(135deg, #0096ff 0%, #0066cc 100%); color: white; padding: 6px 14px; border-radius: 6px; font-family: 'Microsoft YaHei', sans-serif; font-size: 13px; font-weight: bold; z-index: 0; pointer-events: none; border: 2px solid white; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.4); display: none; white-space: nowrap; } /* 气泡箭头 - 匹配气泡底部颜色,向上移动覆盖边框 */ #bubble::after { content: ''; position: absolute; bottom: -8px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: 10px solid #0088dd; } /* 隐藏 Cesium 水印 */ .cesium-viewer-bottom { display: none !important; } </style> </head> <body> <div id="cesiumContainer"></div> <div id="statusMsg">🌍 正在加载地球影像...</div> <div id="bubble">现场编号:H29</div> <script> (async function () { //官网:https://ion.cesium.com/tokens?page=1 , 自行注册 Cesium.Ion.defaultAccessToken = 'xxxxxxxxxxxxxxxxxxxxx'; // ========== 修复点 1:在 Viewer 初始化时配置星空 ========== const viewer = new Cesium.Viewer('cesiumContainer', { infoBox: false, selectionIndicator: false, animation: false, timeline: false, geocoder: false, homeButton: true, sceneModePicker: false, baseLayerPicker: false, navigationHelpButton: false, // 在这里直接配置场景初始状态 sceneOptions: { skyBox: true, // 开启星空 skyAtmosphere: false // 关闭大气层 } }); viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(105, 35, 40000000 ), duration: 0 // 立即跳转,不飞行 }); // ========== 修复点 2:地球显示配置 ========== viewer.scene.globe.show = true; viewer.scene.globe.enableLighting = false; // 初始先不设为透明,或者设为深灰色以便在星空中看到轮廓 viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString('#1a1a2e').withAlpha(0.5); try { const terrain = await Cesium.createWorldTerrainAsync(); viewer.terrainProvider = terrain; } catch (error) { viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider(); } // ============================================= // ✅ 已修改:相机高度翻倍,地球缩小一半 // ============================================= const cameraHeight = 40000000; // 原30000000 → 现60000000 viewer.scene.screenSpaceCameraController.enableRotate = false; viewer.scene.screenSpaceCameraController.enableZoom = false; viewer.scene.screenSpaceCameraController.enableTranslate = false; // 统一计算半径:地球半径 + 相机高度(和旋转函数完全一致) const radius = Cesium.Ellipsoid.WGS84.maximumRadius + cameraHeight; viewer.camera.setView({ // 用统一的半径计算位置 destination: new Cesium.Cartesian3( radius * Math.cos(Cesium.Math.toRadians(30)) * Math.cos(Cesium.Math.toRadians(0)), radius * Math.cos(Cesium.Math.toRadians(30)) * Math.sin(Cesium.Math.toRadians(0)), radius * Math.sin(Cesium.Math.toRadians(30)) ), orientation: { heading: 0.0, pitch: -Cesium.Math.toRadians(45), roll: 0.0 } }); // ========== 修复点 3:恢复启动逻辑,但不把地球变白 ========== setTimeout(() => { // 保留这行是为了让流程继续走,只是不修改 baseColor document.getElementById('statusMsg').innerHTML = '🌍 地球旋转中...'; setTimeout(() => requestAnimationFrame(rotateCamera), 0); }, 1000); // 稍微延迟一点,确保影像先加载出来 let rotationStop = false; let startTime = null; const ROTATION_DURATION = 3000; function rotateCamera() { if (rotationStop) return; if (startTime === null) startTime = performance.now(); const now = performance.now(); const elapsed = now - startTime; if (elapsed >= ROTATION_DURATION) { rotationStop = true; viewer.scene.screenSpaceCameraController.enableRotate = true; viewer.scene.screenSpaceCameraController.enableZoom = true; viewer.scene.screenSpaceCameraController.enableTranslate = true; document.getElementById('statusMsg').innerHTML = '✈️ 正在加载模型...'; setTimeout(() => loadTileset(), 500); return; } const progress = elapsed / ROTATION_DURATION; const longitude = progress * 360; const latitude = 30; const radians = Cesium.Math.toRadians(longitude); const latRadians = Cesium.Math.toRadians(latitude); const radius = Cesium.Ellipsoid.WGS84.maximumRadius + cameraHeight; const cosLat = Math.cos(latRadians); const x = radius * cosLat * Math.cos(radians); const y = radius * cosLat * Math.sin(radians); const z = radius * Math.sin(latRadians); viewer.camera.position = new Cesium.Cartesian3(x, y, z); const center = Cesium.Cartesian3.ZERO; const direction = Cesium.Cartesian3.subtract(center, viewer.camera.position, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(direction, direction); viewer.camera.direction = direction; const north = Cesium.Cartesian3.fromDegrees(longitude, 90, 0); const up = Cesium.Cartesian3.subtract(north, viewer.camera.position, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(up, up); viewer.camera.up = up; viewer.camera.right = Cesium.Cartesian3.cross(viewer.camera.direction, viewer.camera.up, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(viewer.camera.right, viewer.camera.right); requestAnimationFrame(rotateCamera); } try { const labelLayer = viewer.imageryLayers.addImageryProvider( new Cesium.UrlTemplateImageryProvider({ url: 'https://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8', credit: '高德地图' }) ); //labelLayer.alpha = 0.85; } catch (error) { console.warn('标注图层加载失败:', error); } const tilesetUrl = 'http://localhost:8055/3dtiles/tileset.json'; let tileset = null; // 创建圆形canvas作为billboard图像 function createCircleCanvas(size, color) { const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d'); ctx.beginPath(); ctx.arc(size / 2, size / 2, size / 2 - 5, 0, 2 * Math.PI); ctx.fillStyle = color.toCssColorString(); ctx.fill(); ctx.strokeStyle = 'white'; ctx.lineWidth = 5; ctx.stroke(); return canvas; } // 更新气泡位置(固定大小,距离太远时隐藏) let bubblePosition = null; let bubbleUpdateListener = null; function updateBubblePosition(lon, lat, height) { bubblePosition = { lon, lat, height }; const bubble = document.getElementById('bubble'); // 移除旧的监听器 if (bubbleUpdateListener) { viewer.scene.postRender.removeEventListener(bubbleUpdateListener); } // 添加新的监听器 bubbleUpdateListener = function () { if (!bubblePosition) return; // 获取相机高度,用于判断是否隐藏气泡 const cameraHeight = viewer.camera.positionCartographic.height; // 当相机高度超过800米时隐藏气泡 if (cameraHeight > 800) { bubble.style.display = 'none'; return; } const position = Cesium.Cartesian3.fromDegrees( bubblePosition.lon, bubblePosition.lat, bubblePosition.height ); const canvasPosition = viewer.scene.cartesianToCanvasCoordinates(position); if (canvasPosition) { bubble.style.display = 'block'; bubble.style.left = (canvasPosition.x - bubble.offsetWidth / 2) + 'px'; bubble.style.top = (canvasPosition.y - bubble.offsetHeight - 30) + 'px'; } else { bubble.style.display = 'none'; } }; viewer.scene.postRender.addEventListener(bubbleUpdateListener); console.log('气泡位置已设置:', lon, lat, height, '(相机高度>3000米时自动隐藏)'); } // 创建带箭头的气泡canvas function createBubbleCanvas(text) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 设置字体以测量文本宽度 ctx.font = 'bold 18px sans-serif'; const textWidth = ctx.measureText(text).width; // 气泡尺寸 const padding = 20; const bubbleWidth = textWidth + padding * 2; const bubbleHeight = 40; const arrowHeight = 15; const arrowWidth = 20; const borderRadius = 8; canvas.width = bubbleWidth + 10; canvas.height = bubbleHeight + arrowHeight + 5; // 重新设置字体(canvas大小改变后需要重设) ctx.font = 'bold 18px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; // 绘制气泡主体(圆角矩形) const x = 5; const y = 5; ctx.beginPath(); ctx.moveTo(x + borderRadius, y); ctx.lineTo(x + bubbleWidth - borderRadius, y); ctx.quadraticCurveTo(x + bubbleWidth, y, x + bubbleWidth, y + borderRadius); ctx.lineTo(x + bubbleWidth, y + bubbleHeight - borderRadius); ctx.quadraticCurveTo(x + bubbleWidth, y + bubbleHeight, x + bubbleWidth - borderRadius, y + bubbleHeight); // 绘制箭头(指向下方) ctx.lineTo(x + bubbleWidth / 2 + arrowWidth / 2, y + bubbleHeight); ctx.lineTo(x + bubbleWidth / 2, y + bubbleHeight + arrowHeight); ctx.lineTo(x + bubbleWidth / 2 - arrowWidth / 2, y + bubbleHeight); ctx.lineTo(x + borderRadius, y + bubbleHeight); ctx.quadraticCurveTo(x, y + bubbleHeight, x, y + bubbleHeight - borderRadius); ctx.lineTo(x, y + borderRadius); ctx.quadraticCurveTo(x, y, x + borderRadius, y); ctx.closePath(); // 填充气泡背景 ctx.fillStyle = 'rgba(0, 150, 255, 0.95)'; ctx.fill(); // 绘制边框 ctx.strokeStyle = 'white'; ctx.lineWidth = 3; ctx.stroke(); // 绘制文字 ctx.fillStyle = 'white'; ctx.fillText(text, x + bubbleWidth / 2, y + bubbleHeight / 2); return canvas; } // 使用Primitive绘制多边形 - 测试不同高度 function drawPolygonPrimitive(targetLon, targetLat) { console.log('===== 使用Primitive绘制多边形 ====='); // 清除之前的entities viewer.entities.removeAll(); console.log('已清除所有entities'); // 高度-4.4米(贴合模型表面) const testHeight = -4.4; console.log('设置高度:', testHeight, '米(相对于WGS84椭球面)'); // 使用Primitive绘制多边形面 const instance = new Cesium.GeometryInstance({ id: 'polygon-' + Date.now(), // 添加唯一ID geometry: new Cesium.PolygonGeometry({ polygonHierarchy: new Cesium.PolygonHierarchy( Cesium.Cartesian3.fromDegreesArray([ 108.64400909549586, 19.10124543090048, 108.64446593935193, 19.10121679104952, 108.6444605616105, 19.101139271693693, 108.64400371796542, 19.101167911525337 ]) ), height: testHeight, vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.YELLOW.withAlpha(0.8) ) } }); const primitive = viewer.scene.primitives.add(new Cesium.Primitive({ geometryInstances: instance, appearance: new Cesium.PerInstanceColorAppearance({ translucent: true, closed: false }), asynchronous: false // 同步创建,立即生效 })); console.log('多边形Primitive已创建,高度=' + testHeight + '米:', primitive); // 添加边框线 const outlineInstance = new Cesium.GeometryInstance({ id: 'outline-' + Date.now(), geometry: new Cesium.PolygonOutlineGeometry({ polygonHierarchy: new Cesium.PolygonHierarchy( Cesium.Cartesian3.fromDegreesArray([ 108.64400909549586, 19.10124543090048, 108.64446593935193, 19.10121679104952, 108.6444605616105, 19.101139271693693, 108.64400371796542, 19.101167911525337 ]) ), height: testHeight }), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED) } }); const outlinePrimitive = viewer.scene.primitives.add(new Cesium.Primitive({ geometryInstances: outlineInstance, appearance: new Cesium.PerInstanceColorAppearance({ flat: true, renderState: { lineWidth: Math.min(10, viewer.scene.maximumAliasedLineWidth) } }), asynchronous: false })); console.log('边框Primitive已创建'); // 使用HTML div显示气泡,调整高度并支持缩放 updateBubblePosition(targetLon, targetLat, testHeight); console.log('当前entities数量:', viewer.entities.values.length); console.log('===== 绘制完成,高度=' + testHeight + '米,气泡高度=' + bubbleHeight + '米 ====='); } async function loadTileset() { try { tileset = await Cesium.Cesium3DTileset.fromUrl(tilesetUrl); tileset.show = false; viewer.scene.primitives.add(tileset); await tileset.readyPromise; tileset.show = true; locateToModel(); } catch (error) { console.error('加载失败:', error); document.getElementById('statusMsg').innerHTML = '❌ 模型加载失败'; } } async function locateToModel() { if (!tileset) return; try { tileset.show = true; viewer.scene.screenSpaceCameraController.enableRotate = true; viewer.scene.screenSpaceCameraController.enableZoom = true; viewer.scene.screenSpaceCameraController.enableTranslate = true; viewer.scene.screenSpaceCameraController.enableTilt = true; viewer.scene.screenSpaceCameraController.enableLook = true; viewer.scene.screenSpaceCameraController.inertiaSpin = 0.95; viewer.scene.screenSpaceCameraController.inertiaTranslate = 0.95; viewer.scene.screenSpaceCameraController.inertiaZoom = 0.9; viewer.scene.screenSpaceCameraController.minimumZoomDistance = 10; viewer.scene.screenSpaceCameraController.maximumZoomDistance = 50000000; viewer.scene.screenSpaceCameraController.enableCollisionDetection = false; document.getElementById('statusMsg').innerHTML = '✈️ 正在飞向模型...'; // 第一段:飞到模型整体俯视(4秒) await viewer.flyTo(tileset, { duration: 4, offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-50), tileset.boundingSphere.radius * 3.5), easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT }); document.getElementById('statusMsg').innerHTML = '👁️ 查看整体模型...'; await new Promise(resolve => setTimeout(resolve, 1000)); // 目标点位(多边形中心) const targetLon = 108.64423515; const targetLat = 19.10119236; document.getElementById('statusMsg').innerHTML = '🔍 正在放大到目标点位...'; // 第二段:飞到点位1000米(3秒) await viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(targetLon, targetLat, 1000), orientation: { heading: 0, pitch: Cesium.Math.toRadians(-50), roll: 0 }, duration: 3, easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT }); await new Promise(resolve => setTimeout(resolve, 300)); // 第三段:降到200米(2秒) await viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(targetLon, targetLat, 200), orientation: { heading: 0, pitch: Cesium.Math.toRadians(-30), roll: 0 }, duration: 2, easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT }); document.getElementById('statusMsg').innerHTML = '🛬 正在降落到点位...'; // 第四段:降落到点位上方俯视(2秒) let viewHeight = 150; try { const positions = [Cesium.Cartographic.fromDegrees(targetLon, targetLat)]; const updatedPositions = await Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, positions); const terrainHeight = updatedPositions[0].height || 0; viewHeight = terrainHeight + 150; // 地形高度 + 150米,避免太放大 } catch (e) { console.warn('获取地形高度失败', e); } await viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(targetLon, targetLat, viewHeight), orientation: { heading: 0, pitch: Cesium.Math.toRadians(-90), roll: 0 }, duration: 2, easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT }); console.log('飞行完成,开始绘制多边形'); document.getElementById('statusMsg').innerHTML = '🎨 正在绘制标注...'; // 飞行完成后,使用Primitive绘制多边形 try { drawPolygonPrimitive(targetLon, targetLat); console.log('多边形绘制完成'); } catch (e) { console.error('绘制多边形失败:', e); } document.getElementById('statusMsg').innerHTML = '✅ 模型已加载,可交互操作'; setTimeout(() => { document.getElementById('statusMsg').style.opacity = '0'; }, 3000); setupSmartZoom(); } catch (err) { console.error('flyTo 失败:', err); } } function setupSmartZoom() { const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); handler.setInputAction(function (wheelEvent) { const cameraHeight = viewer.camera.positionCartographic.height; if (cameraHeight > 100000) { const zoomAmount = wheelEvent > 0 ? 0.9 : 1.1; const position = viewer.camera.position.clone(); const cartographic = Cesium.Cartographic.fromCartesian(position); const newHeight = cartographic.height * zoomAmount; const clampedHeight = Math.max(10, Math.min(newHeight, 50000000)); const newPosition = Cesium.Cartesian3.fromRadians( cartographic.longitude, cartographic.latitude, clampedHeight ); viewer.camera.position = newPosition; return false; } return true; }, Cesium.ScreenSpaceEventType.WHEEL); } })(); </script> </body> </html>

浙公网安备 33010602011771号