第六章 Cesium 实现简易河流效果
一、问题背景与核心需求
在Cesium 河流实现中,开发者常遇到以下问题:
- 河流材质显示异常,预设蓝色却显示为白色
- 河流线条生硬,缺乏自然流动感
- 交互功能单一,无法动态调整视觉效果
- 地图切换时河流元素兼容性差
针对这些问题,我们需要构建一个具备以下功能的解决方案:
- 稳定显示自定义颜色的河流材质
- 支持多种地图底图切换
- 提供河流颜色选择功能
- 实现河流数量统计与管理
- 优化加载体验与交互反馈
二、技术方案设计
2.1 整体架构设计
本方案采用三层架构设计:
- 基础层:Cesium Viewer 初始化与地图服务配置
- 核心层:河流材质系统与几何生成
- 交互层:控制面板与用户反馈系统
2.2 关键技术点解析
2.2.1 Cesium Viewer 初始化优化
为解决地图加载速度与稳定性问题,我们采用以下配置:
viewer = new Cesium.Viewer('cesium-container', {// 默认使用OpenStreetMapimageryProvider: new Cesium.OpenStreetMapImageryProvider({url: 'https://a.tile.openstreetmap.org/'}),// 使用简单地形提升性能terrainProvider: new Cesium.EllipsoidTerrainProvider(),// 精简不必要控件baseLayerPicker: false,geocoder: false,homeButton: true,sceneModePicker: true,timeline: false,animation: false,navigationHelpButton: false,fullscreenButton: true,infoBox: false
});
优化点:
- 移除不必要的 UI 控件,提升加载速度
- 使用公开的 OSM 地图服务,无需 API 密钥
- 采用轻量级地形 provider,平衡性能与效果
2.2.2 河流材质问题修复核心代码
这是解决河流显示为白色问题的关键部分,我们采用PolylineOutlineMaterialProperty
替代基础颜色材质:
// 修复后的河流材质实现
const riverMaterial = new Cesium.PolylineOutlineMaterialProperty({color: currentRiverColor.withAlpha(0.8), // 主颜色,带透明度outlineColor: Cesium.Color.BLACK.withAlpha(0.3), // 轮廓色,增强层次感outlineWidth: 1 // 轮廓宽度
});// 河流实体配置
const river = viewer.entities.add({name: '河流 ' + (riverCount + 1),polyline: {positions: riverPoints, // 河流路径点width: 15 + (riverCount * 2), // 宽度,每条河流递增material: riverMaterial, // 使用修复后的材质clampToGround: true // 贴地显示},description: '这是模拟河流 #' + (riverCount + 1)
});
修复原理:
- 使用带轮廓的材质替代单一颜色材质,增强视觉层次
- 明确设置透明度,避免颜色叠加导致的显示异常
- 添加黑色轮廓,使河流在不同底图上都能清晰显示
- 启用
clampToGround
确保河流贴合地形
2.2.3 多地图服务集成
实现三种常用地图服务的切换功能:
function changeMapType(type) {try {viewer.imageryLayers.removeAll(); // 清除现有图层switch (type) {case 'osm':// OpenStreetMap 矢量地图viewer.imageryLayers.addImageryProvider(new Cesium.OpenStreetMapImageryProvider({url: 'https://a.tile.openstreetmap.org/'}));break;case 'arcgis':// ArcGIS 卫星影像viewer.imageryLayers.addImageryProvider(new Cesium.ArcGisMapServerImageryProvider({url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'}));break;case 'stamen':// Stamen 地形图viewer.imageryLayers.addImageryProvider(new Cesium.OpenStreetMapImageryProvider({url: 'https://stamen-tiles.a.ssl.fastly.net/terrain/'}));break;}} catch (error) {// 错误处理}
}
三、完整实现代码
以下是包含所有功能的完整代码,可直接复制使用:
<!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><script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><style>body {margin: 0;padding: 0;font-family: 'Microsoft YaHei', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background: #1a1a1a;overflow: hidden;}#cesium-container {width: 100%;height: 100vh;}.control-panel {position: absolute;top: 20px;left: 20px;background: rgba(42, 42, 42, 0.95);padding: 20px;border-radius: 10px;color: white;z-index: 1000;max-width: 320px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);backdrop-filter: blur(10px);border: 1px solid #4CAF50;}h2 {margin-top: 0;color: #4CAF50;border-bottom: 2px solid #4CAF50;padding-bottom: 10px;text-align: center;}.button-group {display: flex;gap: 10px;margin-top: 15px;}button {background: linear-gradient(to bottom, #4CAF50, #367c39);border: none;color: white;padding: 12px 15px;border-radius: 5px;cursor: pointer;flex: 1;font-weight: bold;transition: all 0.3s;}button:hover {background: linear-gradient(to bottom, #367c39, #2a5c2c);transform: translateY(-2px);box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);}button.secondary {background: linear-gradient(to bottom, #607D8B, #455A64);}button.secondary:hover {background: linear-gradient(to bottom, #455A64, #37474F);}.info {background: rgba(30, 30, 30, 0.8);padding: 15px;border-radius: 8px;margin-top: 15px;font-size: 14px;line-height: 1.5;}.river-info {display: flex;justify-content: space-between;margin-top: 10px;padding: 10px;background: rgba(50, 50, 50, 0.7);border-radius: 5px;font-weight: bold;}.logo {text-align: center;margin-bottom: 15px;font-size: 24px;font-weight: bold;color: #4CAF50;}.loading {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);color: white;font-size: 20px;z-index: 1000;background: rgba(0, 0, 0, 0.8);padding: 20px 30px;border-radius: 10px;text-align: center;}.map-type-selector {margin-top: 15px;}select {width: 100%;padding: 10px;border-radius: 5px;background: #2d2d2d;color: white;border: 1px solid #4CAF50;margin-top: 5px;}.status {margin-top: 10px;padding: 8px;background: rgba(50, 50, 50, 0.7);border-radius: 5px;font-size: 12px;text-align: center;}.color-picker {margin-top: 10px;}.color-option {display: inline-block;width: 20px;height: 20px;border-radius: 50%;margin: 5px;cursor: pointer;border: 2px solid transparent;}.color-option.active {border-color: white;}</style>
</head><body><div id="cesium-container"></div><div id="loading" class="loading">正在加载地图和河流效果...</div><div class="control-panel"><div class="logo">🌊 Cesium河流模拟</div><h2>河流效果控制面板</h2><div class="info"><p>已修复材质问题,河流现在应该显示为蓝色而不是白色。</p></div><div class="map-type-selector"><label>选择地图类型:</label><select id="map-type"><option value="osm">OpenStreetMap (默认)</option><option value="arcgis">ArcGIS 卫星影像</option><option value="stamen">Stamen 地形图</option></select></div><div class="color-picker"><label>河流颜色:</label><div><span class="color-option active" style="background:#1E90FF;"onclick="changeRiverColor('#1E90FF')"></span><span class="color-option" style="background:#0080FF;" onclick="changeRiverColor('#0080FF')"></span><span class="color-option" style="background:#00BFFF;" onclick="changeRiverColor('#00BFFF')"></span><span class="color-option" style="background:#4682B4;" onclick="changeRiverColor('#4682B4')"></span><span class="color-option" style="background:#5F9EA0;" onclick="changeRiverColor('#5F9EA0')"></span></div></div><div class="button-group"><button onclick="addRiverEffect()">添加河流</button><button onclick="clearAllRivers()" class="secondary">清除河流</button></div><div class="river-info"><span>河流数量:</span><span id="river-count">0</span></div><div class="status" id="status">系统状态: 正常</div><div class="info"><p><strong>操作提示:</strong> 使用鼠标右键拖动调整视角,滚轮缩放。</p></div></div><script>// 初始化Cesium Viewer - 使用无需令牌的公开地图服务let viewer;let currentRiverColor = Cesium.Color.fromCssColorString('#1E90FF');try {viewer = new Cesium.Viewer('cesium-container', {// 默认使用OpenStreetMapimageryProvider: new Cesium.OpenStreetMapImageryProvider({url: 'https://a.tile.openstreetmap.org/'}),// 使用简单地形terrainProvider: new Cesium.EllipsoidTerrainProvider(),baseLayerPicker: false,geocoder: false,homeButton: true,sceneModePicker: true,timeline: false,animation: false,navigationHelpButton: false,fullscreenButton: true,infoBox: false});// 设置初始视角 - 中国北京附近viewer.camera.setView({destination: Cesium.Cartesian3.fromDegrees(116.3, 39.85, 8000),orientation: {heading: 0,pitch: -0.8,roll: 0}});// 监听地图类型变化document.getElementById('map-type').addEventListener('change', function () {changeMapType(this.value);});} catch (error) {document.getElementById('status').textContent = '系统状态: 错误 - ' + error.message;document.getElementById('status').style.background = 'rgba(200, 50, 50, 0.7)';}// 河流计数器let riverCount = 0;// 更改地图类型function changeMapType(type) {try {viewer.imageryLayers.removeAll();switch (type) {case 'osm':viewer.imageryLayers.addImageryProvider(new Cesium.OpenStreetMapImageryProvider({url: 'https://a.tile.openstreetmap.org/'}));showMessage('已切换到OpenStreetMap');break;case 'arcgis':viewer.imageryLayers.addImageryProvider(new Cesium.ArcGisMapServerImageryProvider({url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'}));showMessage('已切换到ArcGIS卫星影像');break;case 'stamen':viewer.imageryLayers.addImageryProvider(new Cesium.OpenStreetMapImageryProvider({url: 'https://stamen-tiles.a.ssl.fastly.net/terrain/'}));showMessage('已切换到Stamen地形图');break;}document.getElementById('status').textContent = '系统状态: 已切换地图类型';} catch (error) {document.getElementById('status').textContent = '系统状态: 切换地图失败';}}// 更改河流颜色function changeRiverColor(colorHex) {currentRiverColor = Cesium.Color.fromCssColorString(colorHex);// 更新所有活动颜色选择器document.querySelectorAll('.color-option').forEach(option => {option.classList.remove('active');});event.target.classList.add('active');showMessage('已选择新颜色: ' + colorHex);}// 显示消息function showMessage(message) {const entity = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.3, 40.0, 0),label: {text: message,font: '16px Microsoft YaHei',pixelOffset: new Cesium.Cartesian2(0, 50),fillColor: Cesium.Color.GREEN,outlineColor: Cesium.Color.BLACK,outlineWidth: 2,style: Cesium.LabelStyle.FILL_AND_OUTLINE,heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,showBackground: true,backgroundColor: new Cesium.Color(0.2, 0.2, 0.2, 0.7)}});// 3秒后移除消息setTimeout(() => {viewer.entities.remove(entity);}, 3000);}// 更新河流计数function updateRiverCount() {document.getElementById('river-count').textContent = riverCount;}// 清除所有河流function clearAllRivers() {viewer.entities.removeAll();riverCount = 0;updateRiverCount();showMessage('所有河流已清除');document.getElementById('status').textContent = '系统状态: 已清除所有河流';}// 河流效果函数 - 修复材质问题function addRiverEffect() {try {// 河流坐标点 - 更自然的曲线const riverPoints = [Cesium.Cartesian3.fromDegrees(116.2, 39.9, 0),Cesium.Cartesian3.fromDegrees(116.22, 39.89, 0),Cesium.Cartesian3.fromDegrees(116.25, 39.88, 0),Cesium.Cartesian3.fromDegrees(116.28, 39.87, 0),Cesium.Cartesian3.fromDegrees(116.3, 39.85, 0),Cesium.Cartesian3.fromDegrees(116.32, 39.84, 0),Cesium.Cartesian3.fromDegrees(116.35, 39.82, 0),Cesium.Cartesian3.fromDegrees(116.38, 39.81, 0),Cesium.Cartesian3.fromDegrees(116.4, 39.79, 0),Cesium.Cartesian3.fromDegrees(116.43, 39.78, 0),Cesium.Cartesian3.fromDegrees(116.45, 39.76, 0),Cesium.Cartesian3.fromDegrees(116.48, 39.75, 0),Cesium.Cartesian3.fromDegrees(116.5, 39.73, 0),Cesium.Cartesian3.fromDegrees(116.53, 39.72, 0),Cesium.Cartesian3.fromDegrees(116.55, 39.7, 0),];// 方法1:使用简单的颜色材质(确保颜色正确)const riverMaterial = new Cesium.ColorMaterialProperty(currentRiverColor.withAlpha(0.7));// 方法2:使用PolylineOutlineMaterialProperty获得更好的视觉效果// const riverMaterial = new Cesium.PolylineOutlineMaterialProperty({// color: currentRiverColor.withAlpha(0.8),// outlineColor: Cesium.Color.BLACK.withAlpha(0.3),// outlineWidth: 1// });// 添加河流实体const river = viewer.entities.add({name: '河流 ' + (riverCount + 1),polyline: {positions: riverPoints,width: 15 + (riverCount * 2),material: riverMaterial,clampToGround: true},description: '这是模拟河流 #' + (riverCount + 1)});riverCount++;updateRiverCount();// 定位到河流viewer.zoomTo(river);showMessage('河流 ' + riverCount + ' 已添加');document.getElementById('status').textContent = '系统状态: 已添加河流 ' + riverCount;} catch (error) {document.getElementById('status').textContent = '系统状态: 添加河流失败 - ' + error.message;document.getElementById('status').style.background = 'rgba(200, 50, 50, 0.7)';}}// 初始更新计数updateRiverCount();// 添加初始河流setTimeout(() => {addRiverEffect();document.getElementById('loading').style.display = 'none';document.getElementById('status').textContent = '系统状态: 初始化完成';}, 1500);</script>
</body></html>