在三维 GIS 项目里,最常遇到的一类需求不是“展示一个圆”,而是“让用户在地图上自己画一个圆”,用来圈定选址地块、信号覆盖范围、缓冲区分析区域,甚至是做简单的态势标绘。市面上关于 Cesium 绘制圆的资料很多,但多数只停留在“调用 entity.ellipse 显示一个静态圆”的层面。真正到交互绘制、动态半径刷新、样式控制、清理与复用这些环节时,资料往往很零散。本文基于 Vue3 + Cesium 完整梳理一套 Circle 绘图工具实现方案。你可以把它直接集成到业务项目中,也可以理解其原理后迁移到 Rectangle、Polygon、箭头线等其他绘图工具。
文章会从底层概念讲起,先解释 Cesium 里“圆”的本质,再介绍静态圆如何绘制,然后实现完整的鼠标在场景上拉圆动态交互。代码覆盖事件监听、回调属性、相机控制、结果清理等关键部分,并附带常见问题和工程建议。建议读者先跟着代码跑通一个最小 Demo,再结合自己的业务字段去扩展。
1. 背景与核心概念
1.1 Cesium 中“圆”的本质:Ellipse 的特例
很多初学者会去搜索“Cesium Circle”,但真正到了 API 层面会发现,Cesium Entity 中没有一个专门的circle属性。平时说的圆,在 Cesium 内部是用ellipse表示椭圆来实现的,只要让椭圆的长半轴semiMajorAxis和短半轴semiMinorAxis相等,呈现出来的就是一个标准的圆。
圆心的位置由 Entity 的position决定,这个位置是一个Cartesian3类型,对应 WGS84 坐标系下的一个三维坐标点。而半轴semiMajorAxis和semiMinorAxis的单位都是米,不是像素,也不是经纬度差值。这意味着,当你在北京画一个半径 500 米的圆,在东京画同样半径的圆,虽然视觉上经纬度跨度可能略有差异,但实际物理半径是等长的,都代表 500 米。
理解这一步很关键:因为所有动态绘制圆的代码,本质上都是在“动态修改半轴值”。当用户按下鼠标确定圆心后,鼠标在地图上拖出多远,那段距离就会换算成半径,再同时赋给semiMajorAxis和semiMinorAxis,Cesium 就会自动重绘新的圆形。
1.2 动态交互绘制的三个核心部件
如果要实现“按下鼠标起点、拖拽出半径、松开鼠标完成画圆”这样的绘图工具,需要掌握三个核心部件:
第一个是ScreenSpaceEventHandler,它负责监听用户在屏幕上的鼠标操作。由于我们是在三维球上绘图,不能直接拿DOM的click事件来算坐标,而是要把浏览器屏幕坐标交给 Cesium 换算成球面坐标,所以使用handler.setInputAction来注册左键点击、鼠标移动、右键点击等事件。
第二个是绘图状态机。动态绘制工具不能只是“每次点击就画一个圆”,否则用户拖拽鼠标的过程中圆形不会跟随变化。项目中通常定义一个状态字段来记录当前是否处于绘制中,例如_isDrawing。第一次左键进入“绘制中”,鼠标移动时实时更新半径,第二次左键完成绘制,右键取消当前绘制。这套状态机几乎适用于所有矢量绘图形状。
第三个是CallbackProperty。它允许 Entity 的某个属性在每帧渲染时动态变化。我们不需要在鼠标移动时反复删除旧圆再创建新圆,而是把ellipse.semiMajorAxis与ellipse.semiMinorAxis定义为CallbackProperty,每次回调返回当前半径变量。这样既保证了实时刷新效果,又避免了频繁操作 Entity 列表带来的性能和事件泄漏隐患。
1.3 常见应用场景
Circle 绘图工具在三维 GIS 项目中有大量落地场景。最常见的包括:信号覆盖分析,让技术人员在电子地图上画出基站覆盖范围;地块圈选,业务人员通过画圆将某个区域临时标记出来,再进入下一步审批流程;风险影响范围评估,例如通过一个圆框选地质灾害影响半径;以及态势标绘,在军事或应急场景中用圆表示集结区域、观察范围或管制作业半径。
了解真实业务场景后,再去看绘图工具设计,就不会认为 Circle 只是简简单单的几何体。它会牵扯到工具开关、绘制模式、结果数据、撤销重绘和样式风格等多个环节。这也是为什么很多人觉得“画个圆很简单”,但真正做业务功能时仍然要花不少时间的原因。
2. 环境准备与版本说明
2.1 创建 Vue3 + Vite 项目并安装 Cesium
为了便于展示工程化用法,本文示例采用 Vue3 + Vite 作为前端工程基础。如果你的项目是原生 HTML、React 或者其他框架,核心代码也可以直接复用,只需要把CircleDrawTool类所在的模块引入方式改成对应框架的写法即可。
先创建一个 Vite 项目:
npm create vite@latest cesium-circle-demo -- --template vue进入项目目录并安装依赖:
cd cesium-circle-demo npm install npm install cesiumCesium 作为一个大型三维引擎,在构建工具中的资源处理方式会随着版本变化而不同。如果项目运行后在控制台看到 Cesium 的Workers、Assets、Widgets等静态资源加载 404 报错,通常需要配置CESIUM_BASE_URL,或者把node_modules/cesium/Build/Cesium下的相关资源目录复制到public/cesium下。不过这一问题属于工程接入范畴,不影响本文后面绘图逻辑的理解,建议以你当前使用的 Cesium 版本官方文档为准。
为了代码能正常创建三维场景,还需要准备一个 Cesium ion token。可以去 Cesium ion 官网注册后创建一个 token,然后在代码中设置:
import * as Cesium from 'cesium'; Cesium.Ion.defaultAccessToken = '替换成你自己的token';如果没有配置 token,Viewer 默认加载的在线底图可能会失败。你可以换成自己项目中的离线瓦片服务,也可以先确保能访问 Cesium ion 再继续后续示例。
2.2 初始化 Viewer
在一个 Vue 组件中,初始化 Viewer 的代码通常放在onMounted生命周期中。因为此刻 DOM 容器已经渲染完成,才能把 Cesium 场景挂载到对应的div上。
先准备一个占满全屏的容器:
<template> <div ref="cesiumContainer" class="cesium-container"></div> </template> <style scoped> .cesium-container { width: 100%; height: 100vh; margin: 0; padding: 0; } </style>然后在组件脚本中初始化:
import { ref, onMounted } from 'vue'; import * as Cesium from 'cesium'; const cesiumContainer = ref(null); let viewer = null; onMounted(() => { if (!cesiumContainer.value) return; viewer = new Cesium.Viewer(cesiumContainer.value, { animation: false, timeline: false, baseLayerPicker: false, geocoder: false, homeButton: false, sceneModePicker: false, navigationHelpButton: false, fullscreenButton: false, }); // 将视角定位到北京 viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(116.391, 39.907, 15000), }); });这里关闭了大部分默认控件,是为了让界面更聚焦于绘图工具。如果你希望保留右上角的工具按钮,把对应选项设为true或不传即可。
2.3 规划代码结构
绘图功能如果全部写进组件里,代码会很臃肿,也不便于其他页面复用。本文采用独立工具类的设计思路,最终项目结构如下:
src/ ├── components/ │ └── CircleMap.vue ├── utils/ │ └── drawCircleTool.js └── main.jsdrawCircleTool.js