
简介面向前端开发者与三维可视化爱好者这份资源围绕 Cesium 库与卫星雷达数据的组合展示适合想要快速上手三维地球场景、学习遥感数据可视化入门实践的读者。压缩包共十一个文件包含两个可直接运行的 HTML 页面分别演示气象卫星动态图与气象雷达动态图另有九张 PNG 格式的雷达影像作为样例数据总大小约 2.88MB结构清晰便于对照查看。目前已有 1904 人浏览学习。通过这些示例可以了解如何以 HTML 承载 Cesium 三维场景学习加载影像图层、控制相机视角、实现图层动态更新等核心 API 的用法也能熟悉卫星雷达切片在 Web 端的基本呈现方式并理解坐标投影与数据预处理的大致流程。资源体积小巧适合本地运行验证也为后续接入真实遥感数据源或进一步做性能优化提供了可扩展的起点。1. 卫星雷达上屏Cesium里被低估的三维前端场景提到卫星雷达可视化多数前端开发的第一反应是「这得靠GIS后端出图」或者「至少要拉上Three.js手写一堆着色器」。实际上在Cesium这个专为地理空间设计的WebGL引擎里用纯HTML加JavaScript就能把卫星雷达的三维场景完整搭起来波束锥体、旋转扫描线、卫星轨道连线、地面光斑、目标锁定全部用Entity和CallbackProperty组合就能落地不需要后端参与也不涉及自己封装WebGL渲染管线的复杂工作。这个示例的典型应用包括卫星地面站监控大屏、航天任务可视化预演、电磁频谱态势展示。适合已经会加载地球但还没碰过雷达波束类特效的前端工程师也适合想在面试或开源作品里放一个交互感强烈的三维Demo的开发者。下文从场景初始化开始逐步拆解雷达效果的核心实现。2. Cesium场景基座从HTML页面到可用的三维雷达画布2.1 为什么卫星雷达展示选择Cesium而不是Three.js在动手写代码之前先把选型问题说明白。卫星雷达的可视化对象——雷达站、波束锥、卫星轨道、地面影像、地形——本质上是带地理坐标的要素它们需要被投影在地球表面或者绕地轨道上。Three.js没有地理坐标系概念所有物体都活在局部笛卡尔坐标里你要自己处理WGS84经纬度和笛卡尔坐标的互相换算还要解决地球曲率、影像底图、相机绕地旋转等一系列GIS基础设施问题。Cesium天生支持经纬度到Cartesian3的转换并且内置影像底图、地形、时间轴这对卫星轨道随时间动态变化的场景是刚需。所以Cesium是这个场景里性价比最高的框架除非你的项目完全不关心地理坐标只想画一个抽象的三维雷达特效。这个判断对后续所有代码都成立因为下面每一步都在跟坐标系和世界坐标打交道。把Cesium定位成“带地球底图的WebGL引擎”是很常见的一个误解——它真正的能力是地理坐标体系下的空间计算和可视化编排雷达波束只是这套能力的一个应用切片。2.2 初始化一个干净的Viewer实例Cesium的引入方式有两种主流选择通过npm安装后由构建工具打包或者直接在HTML里用script标签引用CDN。这个示例走的是后者因为雷达这类单页面Demo用CDN引入最直接不涉及打包配置打开HTML文件就能运行。如果你用npm路径替换为node_modules/cesium/Build/Cesium下的对应文件即可。!DOCTYPE html html langzh-cn head meta charsetutf-8 titleCesium Satellite Radar Demo/title style html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } /style !-- 按实际引入路径调整 -- script srcnode_modules/cesium/Build/Cesium/Cesium.js/script link hrefnode_modules/cesium/Build/Cesium/Widgets/widgets.css relstylesheet /head body div idcesiumContainer/div script srcradar.js/script /body /html#cesiumContainer设置为全屏容器Cesium的Widget会自动适配容器尺寸无需额外监听resize事件。如果使用CDN方式把script和link的路径换成对应版本的地址注意前后版本要一致否则可能出现Cesium.js加载成功但widgets.css样式缺失的界面错乱。接下来创建Viewer实例。雷达场景需要一个干净的视野。默认Cesium会开启太阳光照和雾效这些会让雷达波束的纯色线条看起来发灰甚至发白所以初始化时要逐项关掉const viewer new Cesium.Viewer(cesiumContainer, { timeline: false, // 隐藏时间轴 animation: false, // 隐藏动画控件 baseLayerPicker: true, // 保留影像底图切换 sceneModePicker: true, // 保留2D/3D切换 infoBox: false, // 关闭点击实体弹窗 selectionIndicator: false,// 关闭选中光圈 requestRenderMode: true, // 按需渲染 maximumRenderTimeChange: 0.5 }); viewer.scene.globe.enableLighting false; viewer.scene.fog.enabled false; viewer.scene.highDynamicRange false; viewer.scene.backgroundColor Cesium.Color.fromCssColorString(#0a0e24);requestRenderMode: true是性能关键项它让Cesium只在场景内容变化时重绘而不是始终以60帧速率渲染。对雷达扫描这种事件驱动的可视化场景按需渲染能把GPU占用压到很低。maximumRenderTimeChange: 0.5配合前者使用表示摄像机或场景变化超过0.5秒才触发一次重绘实际交互中如果觉得视角转动不跟手改成0.1即可。后三行场景配置直接影响雷达的显示效果。enableLighting false关闭地形和模型的实时光照否则波束锥体会因为法线方向不同产生明暗分界线看起来像几何体破损而不是完整的半透明锥。fog.enabled false去掉地平线雾效避免雷达扫描线在远处被雾化到肉眼不可见。highDynamicRange关闭HDR色调映射让雷达的纯色材质在背景中保持原始饱和度。2.3 雷达站点的坐标与视角预置雷达站是波束、扫描线、卫星连线三者的公共锚点。常见做法是把站点的经纬度、高度写成配置对象便于后续参数化和多站扩展const radarSite { position: Cesium.Cartesian3.fromDegrees(116.391, 39.907, 86), height: 500000, // 波束最大高度米 range: 1500000 // 扫描半径米 }; viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(116.391, 39.507, 1200000), orientation: { heading: 0, pitch: Cesium.Math.toRadians(-45), roll: 0 } });视角预置有一个很容易踩的细节destination的纬度要比雷达站低一点把相机和目标点拉开一段距离。如果相机初始位置放在雷达站正上方倾斜视角拉不出来屏幕上就是一张平面俯视图波束锥的立体感完全消失。pitch使用弧度制-45度俯视能同时看到波束锥体和地面投影是雷达Demo里比较通用的观察角度。如果想强调卫星轨道全貌可以把pitch调小到-30度视野更高更远。radarSite.position通过fromDegrees方法把经纬度和海拔高度转成Cesium世界坐标Cartesian3这是Entity.position要求的类型。第三个参数86是海拔米数这里给了一个接近地面的值实际项目中要填雷达站真实海拔否则波束底部会嵌进地形。2.4 Viewer配置项速查配置项默认值雷达场景建议作用requestRenderModefalsetrue按需渲染降低GPU占用timelinefalsefalse隐藏时间轴控件animationfalsefalse隐藏动画控件infoBoxtruefalse关闭点击实体信息弹窗baseLayerPickertruetrue保留影像底图切换sceneModePickertruetrue保留2D/3D视图切换selectionIndicatortruefalse关闭单击选中光圈geocodertruefalse关闭搜索框这个表在实际项目中可以直接当checklist用。baseLayerPicker建议保留雷达场景里经常需要切换影像底图来分析地形遮挡关系geocoder关闭是因为演示场景里搜索框毫无用处还会挡住右上角的相机控制按钮。注意requestRenderMode开启后如果你在代码里直接修改了相机位置但没有调用viewer.scene.requestRender()画面会卡在上一帧这个坑在后文的排错章节还会提到。3. 雷达扫描锥与动态波束的实现3.1 用CylinderGeometry实现波束锥体雷达波束本质是一个从站点向太空扩张的圆锥体。Cesium没有现成的锥体几何类但可以用CylinderGeometry近似——把圆柱的topRadius设为0就变成锥体。这个技巧在Cesium社区是雷达效果的标准做法因为CylinderGeometry是GPU原生支持的几何体绘制效率比手动拼三角形高得多。const beam viewer.entities.add({ name: 雷达波束, position: radarSite.position, orientation: Cesium.Transforms.headingPitchRollQuaternion( radarSite.position, new Cesium.HeadingPitchRoll(0, 0, 0) ), cylinder: { length: radarSite.height, topRadius: 0, // 顶部收敛为锥尖 bottomRadius: radarSite.range * Math.tan(Cesium.Math.toRadians(30)), material: Cesium.Color.RED.withAlpha(0.25), outline: true, outlineColor: Cesium.Color.RED.withAlpha(0.8), numberOfVerticalLines: 12 } });Cesium的CylinderGeometry是有向几何体长度方向沿局部坐标系的Z轴因此必须给Entity设置orientation否则锥体会水平躺在地球切线方向看起来像一根横放的喇叭。bottomRadius的计算用了range * tan(30°)30度是波束的半张角可以按雷达实际的天线波束宽度修改。outline和numberOfVerticalLines控制锥体轮廓线的密度12条网格线能让锥体结构清晰可辨如果追求更简洁的视觉可以关掉outline只保留半透明材质。3.2 动态扫描线CallbackProperty实现旋转雷达扫描线是整个雷达效果的核心视觉。实现方式是CallbackProperty绑定一个随时间变化的heading角驱动一个刀片状的薄片锥体反复旋转扫过。CallbackProperty会在每帧渲染时调用回调函数获取最新值来更新Entity属性这正是动态效果的机制。let scanAngle 0; const scanSpeed 0.5; // 弧度/秒 const scanLine viewer.entities.add({ position: radarSite.position, orientation: new Cesium.CallbackProperty(() { return Cesium.Transforms.headingPitchRollQuaternion( radarSite.position, new Cesium.HeadingPitchRoll(scanAngle, 0, 0) ); }, false), cylinder: { length: radarSite.height, topRadius: 0, bottomRadius: radarSite.range * Math.tan(Cesium.Math.toRadians(30)), material: Cesium.Color.CYAN.withAlpha(0.15), outline: false, slices: 2 // 两个侧面组成薄片 } }); viewer.clock.onTick.addEventListener(() { scanAngle (scanAngle scanSpeed * viewer.clock.deltaTime) % (Math.PI * 2); });CallbackProperty的第二个参数必须设为false表示该属性不是常量需要每帧重新计算。这是新手最容易出错的地方——如果传trueCesium会认为回调结果不变只在首次求值时执行一次回调扫描线就会永远静止。slices: 2让圆柱退化为只有两个侧面的薄片视觉上就是刀片状的扫描线。scanSpeed单位是弧度/秒0.5意味着大约12.5秒转完一整圈演示效果适中做快速扫描可以加大到1.5或2.0。viewer.clock.onTick是Cesium的时钟事件每帧触发一次。用viewer.clock.deltaTime当前帧与上一帧的时间差单位秒乘以速度可以保证扫描线的转速不受渲染帧率波动影响。如果直接每帧加固定角度在高刷新率屏幕上雷达飞快旋转低刷新率屏幕上却慢吞吞体验很不一致。3.3 卫星轨道与照射关联有了波束还需要一个被照射的目标——卫星。这里用一个动态移动的点表示卫星再通过雷达站到卫星的连线模拟照射关系。卫星轨道可以用多种方式计算简化版本用圆形轨道近似工程版本用TLE轨道外推Demo场景里圆形轨道足够展示效果。function computeOrbitPosition(time) { const seconds time.secondsOfDay; const angularSpeed 2 * Math.PI / 5400; // 90分钟一圈 const orbitRadius 700000; // 轨道高度700km const lon 116.391 seconds * 0.004; const lat 39.907 Math.sin(seconds * angularSpeed) * 10; return Cesium.Cartesian3.fromDegrees(lon, lat, orbitRadius); } const satellite viewer.entities.add({ position: new Cesium.CallbackProperty(() { return computeOrbitPosition(viewer.clock.currentTime); }, false), point: { pixelSize: 8, color: Cesium.Color.GOLD, outlineColor: Cesium.Color.WHITE, outlineWidth: 2 }, label: { text: SAT-01, font: 12px monospace, pixelOffset: new Cesium.Cartesian2(0, -16) } }); const link viewer.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() { return [ radarSite.position, computeOrbitPosition(viewer.clock.currentTime) ]; }, false), width: 1.5, material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.2, color: Cesium.Color.CYAN }) } });computeOrbitPosition是一个简化的圆形轨道模型。time.secondsOfDay返回当天已经过去的秒数用它驱动卫星在经纬度两个维度上运动经度缓慢东移模拟轨道面的进动纬度方向用正弦函数模拟倾角带来的上下摆动。这个函数在真实项目里可以替换成TLE或JPL星历计算接口不变Entity的其他代码完全不需要改动。PolylineGlowMaterialProperty是Cesium内置的发光材质比普通纯色材质更适合表示雷达波束照射卫星时的能量感。glowPower控制光晕强度0.1到0.3之间比较合适太大会让线条边缘模糊到看不清走向。width: 1.5在1080P屏幕上是一条清晰的细线在4K屏幕上可能需要提到2.5到3像素。3.4 地面光斑与波束锥体错位排查为了让雷达可视化更完整可以在雷达站正下方的地面上贴一个椭圆光斑模拟波束足印。实现方式是添加一个ellipse实体并让它的旋转角与扫描线同步const groundSpot viewer.entities.add({ position: radarSite.position, orientation: new Cesium.CallbackProperty(() { return Cesium.Transforms.headingPitchRollQuaternion( radarSite.position, new Cesium.HeadingPitchRoll(scanAngle, 0, 0) ); }, false), ellipse: { semiMajorAxis: radarSite.range, semiMinorAxis: radarSite.range * 0.6, material: Cesium.Color.CYAN.withAlpha(0.1), height: 0 } });height: 0表示光斑贴在椭球表面上。这里的椭圆半轴要与波束锥体底部半径保持一致或成正比例否则光斑和锥体会错位。调试时建议先把scanSpeed临时调成0看光斑的长轴是否与锥体剖面的中线对齐。如果不齐优先检查波束半张角或range参数是否传错。4. Entity、Primitive与坐标系排障4.1 Entity和Primitive的取舍项目中的取舍标准其实不难Entity适合动态交互、数据量小、需要点击拾取的场景Primitive适合静态或半静态、大量实例、需要极致性能的场景。核心差异在于Entity在每个属性变化时可能触发整个对象的重建而Primitive的几何体是一次性提交GPU后续修改单个实例需要重建实例列表。因此雷达扫描锥如果只需要显示几秒钟用Entity完全够如果要同时渲染几十个地面站的雷达波束就必须改为Primitive批量绘制。const instances radarSites.map(site { return new Cesium.GeometryInstance({ geometry: new Cesium.CylinderGeometry({ length: site.height, topRadius: 0, bottomRadius: site.range * Math.tan(Cesium.Math.toRadians(30)) }), modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame( site.position, new Cesium.HeadingPitchRoll(0, 0, 0) ), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.CYAN.withAlpha(0.3) ) } }); }); const primitive new Cesium.Primitive({ geometryInstances: instances, appearance: new Cesium.PerInstanceColorAppearance({ transparent: true }) }); viewer.scene.primitives.add(primitive);Primitive版本里modelMatrix的作用是把局部坐标系的锥体平移到雷达站点并旋转到指定朝向替代Entity里直接传position的方式。PerInstanceColorAppearance支持每个实例独立颜色通过ColorGeometryInstanceAttribute.fromColor把颜色写入实例属性。这个写法的优势是50个站点的波束只需一次Draw Call而Entity版本每个站点是一个独立的绘制单元。代价是动态修改不方便——想单独改变某一个站点的波束颜色需要重建整个实例数组。4.2 按需渲染模式下的性能陷阱雷达扫描线的CallbackProperty每帧更新这本身就会触发场景重绘所以requestRenderMode在这种场景下只是让静态部分减少无用渲染。实际操作中应区分静态和动态Entity静态波束锥体用Primitive动态扫描线和卫星连线用Entity。这样组合后静态部分不会参与每帧的Entity属性求值性能收益明显。按需渲染模式还有一个高频坑快速旋转视角或缩放之后画面会卡在旧帧。原因是请求渲染的信号没有在摄像机操作结束后被触发。补丁做法是监听相机的moveEnd事件手动请求一帧viewer.scene.camera.moveEnd.addEventListener(() { viewer.scene.requestRender(); });这段代码在相机停止移动时强制重绘一次避免画面停留在拖拽前的旧帧。凡是代码中修改了卫星轨道参数或者雷达站点位置也要在修改后调用一次viewer.scene.requestRender()否则下一次重绘可能迟迟不来。4.3 坐标系转换的三个典型错误雷达示例里最容易翻车的坐标系问题有三个。第一把经纬度数组直接当Cartesian3用Entity.position只接受Cartesian3必须经过fromDegrees转换。第二headingPitchRollQuaternion的第一个参数同样要求Cartesian3有人传入[116, 39]数组导致方向错乱且难以排查。第三在CallbackProperty回调里做坐标转换时不要在回调外部缓存结果。// 错误示范回调外缓存坐标 const cachedPos Cesium.Cartesian3.fromDegrees(116, 39); satellite.position new Cesium.CallbackProperty(() { return cachedPos; // 卫星永远不动 }, false); // 正确做法回调内动态计算 satellite.position new Cesium.CallbackProperty(() { const t viewer.clock.currentTime.secondsOfDay; const lon 116 t * 0.001; return Cesium.Cartesian3.fromDegrees(lon, 39, 600000); }, false);第一段代码看起来没毛病但cachedPos是常量回调每次返回同一个坐标卫星在场景初始化后就不会移动。第二段代码在回调内部用secondsOfDay实时计算经纬度卫星才会沿经度方向连续运动。CallbackProperty的回调本质上是纯计算过程不要在回调里依赖外部可变状态除非你非常清楚那个状态会在什么时机被更新。4.4 雷达波束与地形的深度测试问题雷达波束被地形或3D Tiles模型遮挡时会出现波束穿地或者完全看不见的现象。这是深度测试策略的问题。透明材质在Cesium中默认参与深度测试但半透明波束和不透明地形混合时渲染顺序会将两者分离开导致波束的一部分被地形盖住。viewer.scene.globe.depthTestAgainstTerrain false; // 或针对单个Entity禁用深度测试距离 beam.cylinder.disableDepthTestDistance 1000000;提示disableDepthTestDistance只对Entity属性生效Primitive的深度测试要通过Appearance.depthTestEnabled属性单独控制。depthTestAgainstTerrain false是全局开关关闭后地形起伏不再遮挡任何实体但地形下的数据也会叠在表面显示视觉上出现穿模。disableDepthTestDistance是更精细的控制方式它设置一个距离阈值在这个距离范围内的实体片段不参与深度测试。雷达波束从站点延伸几百公里设置100万米的阈值足以保证它显示在3D Tiles模型和地形之上同时又不影响远处其他实体的正常遮挡关系。5. 通用化把示例改造成可复用的雷达可视化组件5.1 参数化配置设计前面的代码是过程式写法适合单Demo演示。实际项目里雷达可视化常常是运维大屏的一个模块需要同时展示多个雷达站、多套参数。把效果封装成类把中心坐标、波束高度、扫描半径、扫描速度、颜色都做成配置项是最容易想到也最好维护的演进方向。class RadarBeam { constructor(viewer, options) { this.viewer viewer; this.site Cesium.Cartesian3.fromDegrees( options.lon, options.lat, options.height ); this.range options.range || 1000000; this.beamHeight options.beamHeight || 500000; this.sweepSpeed options.sweepSpeed || 0.3; this.color options.color || Cesium.Color.CYAN; this.entities []; this.scanAngle 0; this.build(); } build() { // 创建波束锥体和扫描线加入this.entities const beam this.viewer.entities.add({ /* 省略参照第3章 */ }); this.entities.push(beam); this._tickHandler () { this.scanAngle (this.scanAngle this.sweepSpeed * this.viewer.clock.deltaTime) % (Math.PI * 2); }; this.viewer.clock.onTick.addEventListener(this._tickHandler); } setColor(color) { this.color color; this.entities.forEach(e { if (e.cylinder) e.cylinder.material color.withAlpha(0.2); if (e.polyline) e.polyline.material color.withAlpha(0.8); }); } destroy() { this.viewer.clock.onTick.removeEventListener(this._tickHandler); this.entities.forEach(e this.viewer.entities.remove(e)); this.entities []; } }封装后新建一个雷达站只需要new RadarBeam(viewer, options)。setColor方法在实战里经常用于状态切换——雷达锁定时变红、正常扫描时变青。destroy方法必须移除tick监听器否则雷达站删除后扫描角还在后台更新造成无效计算和潜在的内存泄漏。5.2 与3D Tiles叠加雷达站周围如果有倾斜摄影模型等3D Tiles数据需要把波束的渲染顺序处理好。前文提到的方法是设置disableDepthTestDistancebeam.cylinder.disableDepthTestDistance 1000000;这个参数的单位是米表示在该距离范围内的深度测试被禁用。设为比波束最大高度大一个量级的值雷达效果就会始终显示在模型之上。实际工程中如果雷达站和倾斜摄影模型距离很近可以再配合viewer.scene.globe.depthTestAgainstTerrain false使用但要清楚这个全局开关会让所有实体不再被地形遮挡。5.3 一个快速验证技巧把扫描速度调到接近0比如sweepSpeed: 0.01然后手动旋转视角观察波束锥体是否与卫星连线完全重合。如果锥体边缘和连线之间有偏差问题几乎必然出在坐标转换的时点上——检查不同CallbackProperty里取的是否是同一个tick的时间位置求值的时钟不同步是视觉错位的常见根因。用浏览器Performance面板录制几秒可以查看Cesium.Entity的更新耗时。通常瓶颈集中在CylinderGeometry重建上单帧耗时超过10ms时把静态锥体拆出来用Primitive渲染只保留扫描线用Entity即可。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。