在 Cesium 项目里,“圆”可能是我见过开发频率最高的图元之一。很多业务场景都会先画一个半径范围:雷达扫描范围、风电场影响圈、安全警戒区、通勤圈、地块覆盖范围等。最初我习惯把圆心和半径写成常量,直接塞进一个ellipse,但等到需求变成“让业务人员在三维场景里自己动态绘制一个圆”时,事情就变得不太一样了:鼠标点哪、半径怎么算、预览怎么实时更新、取消和完成如何区分、圆是贴地还是贴模型,这些细节如果没处理好,后期会一直返工。
这篇文章就以Cesium 绘图工具 - Circle为切入点,从底层的EllipseGraphics概念讲到一套可直接复用的交互工具实现,覆盖圆心拾取、动态半径计算、实时预览、完成回调,以及后续如何接 Vue3 项目或把结果转成 GeoJSON。新手可以直接拿来学习,有一定经验的开发者也可以把里面的交互状态机抽取出来,扩展成矩形、多边形、高德箭头线等其他绘图工具。
1. 认识 Cesium 中的 Circle 绘图
1.1 Circle 的本质是 Ellipse
Cesium 没有专门的CircleGraphics这样一个类,你可能会在中文文档或英文文档里找不到“Circle”这个独立 API。通常我们说的“画圆”,在 Cesium Entity API 里实际上是用椭圆图元ellipse实现的:只要让椭圆的semiMajorAxis(长半轴)和semiMinorAxis(短半轴)相等,它在视觉上就是一个标准圆。
viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(116.3913, 39.9075), ellipse: { semiMajorAxis: 5000, // 长半轴,单位:米 semiMinorAxis: 5000, // 短半轴,单位:米 material: Cesium.Color.CYAN.withAlpha(0.4), outline: true, outlineColor: Cesium.Color.WHITE, outlineWidth: 2 } });如果你只想画一个空心的圆形“边界线”,不需要填充面,可以设置fill: false,当然也可以保留半透明填充,这在业务上通常更清晰。
1.2 为什么不能直接传一个“像素半径”或“经纬度半径”
很多刚开始接触 Cesium 的同学会把“圆的大小”想象成屏幕上多少像素,或者把半径理解成经纬度差值。但 Cesium 的三维场景是建立在椭球体模型上的,所以ellipse.semiMajorAxis和semiMinorAxis的单位都是米,圆心位置的坐标系是 Cartesian3(世界坐标),而不是单纯的经纬度。
也就是说,如果我们想得到一个真实的 5 公里范围圆,前提是拿到一个真实的空间距离 5000 米。如果直接从屏幕上两点之间通过百分比换算半径,很容易在地图缩放级别不同时得到完全不一致的结果,比如在北京和在赤道附近同样拖动 100 像素,实际代表的半径可能差好几倍。
1.3 Circle 的常见应用场景
- 雷达探测范围展示:用一个半透明圆表示雷达最大探测距离。
- 规划禁入区:在 GIS 场景中给操作员提供绘制圆形禁飞区、禁入区的能力。
- 影响半径分析:加油站服务半径、学校通勤半径、商业选址影响范围等。
- 风电或光伏项目覆盖圈:围绕风机的噪音影响范围、生态红线范围。
- 三维可视化教学:帮助初学 Cesium 的人理解坐标拾取、椭圆体计算、CallbackProperty 动态更新等核心概念。
从架构上讲,Circle 是最容易实现的绘图工具,非常适合作为编码入门案例。理解它之后,再去看 Cesium 中如何实现矩形、多边形、圆柱体、可视域分析、雷达光波,甚至自定义线材质,很多思路都是同构的。
2. 环境准备与基础工程搭建
2.1 技术栈版本说明
我下面给出的示例基于原生 HTML + JavaScript + 本地 Cesium 包,重点演示绘图工具本身的代码,不依赖任何框架。如果你使用的是 Vue3 + Vite,只需要把绘图工具类单独抽成 JS 文件,在生命周期函数里创建Viewer和工具实例即可,核心逻辑完全一致。
Cesium 本身迭代速度比较快,不同小版本的 API 会有细微差异。因此本文不写死某一个固定版本号,请以你项目中实际安装的 Cesium 包为准,建议使用较新的 1.x 稳定版本。在本地运行时,请确保能通过静态资源路径访问到 Cesium 的Cesium.js和widgets.css。
2.2 最小可运行页面
为了便于完全复现,我先搭建一个最基础的index.html。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>Cesium Circle Draw Tool</title> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; overflow: hidden; } .toolbar { position: absolute; top: 20px; left: 20px; z-index: 999; background: rgba(255, 255, 255, 0.9); padding: 10px 16px; border-radius: 6px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } .toolbar button { padding: 6px 14px; cursor: pointer; } </style> <link rel="stylesheet" href="./node_modules/cesium/Build/Cesium/Widgets/widgets.css" /> <!-- 如果你的 Cesium 不是通过 npm 安装,请改成实际静态资源路径 --> <script> window.CESIUM_BASE_URL = "./node_modules/cesium/Build/Cesium/"; </script> <script src="./node_modules/cesium/Build/Cesium/Cesium.js"></script> <script src="./src/drawCircleTool.js"></script> </head> <body> <div id="cesiumContainer"></div> <div class="toolbar"> <button id="drawCircleBtn">绘制圆形</button> <button id="clearCircleBtn">清空</button> </div> <script> Cesium.Ion.defaultAccessToken = "在这里填入你的 Cesium Ion Token"; const viewer = new Cesium.Viewer("cesiumContainer", { baseLayerPicker: false