Cesium 军事标绘入门:用 Cesium-Plot-JS 快速实现标绘功能
在军工、应急指挥、国土安全等项目中,常常需要在三维场景中标记 “攻击路线”“防御区域”“集结点” 等具有军事含义的要素 —— 这就是军事标绘。它本质是 “三维 GIS 绘制工具” 的专项延伸,也是GIS开发者从 “基础绘制” 进阶到 “行业专项功能” 的关键知识点。
军事标绘相比普通绘制(如画点、线、面),军事标绘有更严格的符号规范(如箭头角度、线条样式需符合军事标准)。因此如果直接用原生 Cesium 实现军事标绘,需要手动处理 “鼠标事件监听、坐标转换、几何算法计算、图元渲染” 等全流程,开发难度高、周期长。
这里我们可以用开源的Cesium插件(Cesium-Plot-JS)来实现,需要注意的是,这个插件适配的cesium版本为1.99,但是我们的1.97也可以适配。
本系列文章将从 “实战使用” 到 “原理拆解”,分三篇带你全面掌握 Cesium 军事标绘:
第一篇聚焦 “Cesium-Plot-JS 基础”,快速实现军事标绘功能;
第二篇深入 “数据驱动与功能拓展”,用已有数据生成对应的军事标绘
第三篇剖析 “底层原理与架构设计”,让你从 “会用” 升级为 “理解并能自定义”。
一、环境搭建步骤
1. 1 安装依赖
首先通过包管理器安装 Cesium-Plot-JS,同时需确保项目已引入 Cesium 核心库(1.97/1.99 版本)、dat.GUI(用于调试界面)与 cesium-navigation-es6(用于罗盘 / 比例尺控件):
// 使用pnpm安装(npm/yarn同理)
pnpm i cesium-plot-js
1. 2 配置 Cesium Token
Cesium 加载底图需依赖 Ion Token,需先在Cesium Ion 官网申请 Token,再在代码中配置:
// 引入核心库
import * as Cesium from "cesium";
import * as dat from "dat.gui";
import CesiumNavigation from "cesium-navigation-es6";
import CesiumPlot from "cesium-plot-js";
// 配置Cesium Token(建议通过环境变量注入,避免硬编码)
Cesium.Ion.defaultAccessToken = import.meta.env.VITE_CESIUM_TOKEN;
二、标绘工具激活
本节将逐步实现 “Cesium 场景搭建→标绘工具集成→事件监听” 的完整流程,以最常用的 “细箭头” 标绘为例,演示基础用法。
2.1 初始化 Cesium Viewer
首先创建 Viewer 实例,并配置界面控件(隐藏不必要的时间轴、底图切换等控件,聚焦标绘功能):
//使用cesium默认配置 初始化viewer
const viewer = new Cesium.Viewer("cesiumContainer", {timeline: false, //设置默认的时间轴不显示animation: false, //隐藏动画控件baseLayerPicker: false, //隐藏底图切换geocoder: false, //隐藏导航功能homeButton: false, //复位按钮sceneModePicker: false, //二三维切换按钮navigationHelpButton: false, //隐藏帮助按钮scene3DOnly: true, // 如果是三维的系统,最好加上这个配置shouldAnimate: true, //最好设置动画为true
});
// 快速实现比例尺,罗盘
new CesiumNavigation(viewer, {defaultResetView: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 20000000),enableCompass: true,enableZoomControls: true,enableDistanceLegend: true,
});
// 1.97版本加载3dtiles
const tileset = new Cesium.Cesium3DTileset({url: new Cesium.IonResource.fromAssetId(69380),
});
// 将模型加入场景中
viewer.scene.primitives.add(tileset);
// 监听模型加载完成的回调,将视角注视到模型
tileset.readyPromise.then((res) => {viewer.zoomTo(tileset);
});
2.2 激活标绘工具(以细箭头为例)
然后添加一个gui工具,我们点击按钮可以激活对应的绘制工具,这里拿官网上的demo测试一下
// 创建dat.GUI调试面板
const gui = new dat.GUI();
// 添加“激活细箭头”按钮
gui.add({
fn() {
// 初始化细箭头标绘,配置样式
const fineArrow = new CesiumPlot.FineArrow(Cesium, viewer, { material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"), // 填充色(半透明蓝)
outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"), // 轮廓色(纯蓝)
outlineWidth: 3, // 轮廓宽度 });
}
},"fn").name("激活细箭头标绘");
在new了CesiumPlot.FineArrow之后会自动触发sse事件来绘制军标
我们还可以通过事件来监听绘制的结果和编辑的结果,这个api设计和我们的绘制工具很相似
geometry.on("drawStart", () => {
console.log("开始绘制");
});
geometry.on("drawUpdate", (data) => {
console.log("绘制中", data);
});
geometry.on("drawEnd", (data) => {
console.log("结束绘制", data);
});
geometry.on("editStart", (data) => {
console.log("开始编辑", data);
});
geometry.on("editEnd", (data) => {
console.log("编辑结束", data);
});
可以看到在绘制过程中,返回了当前鼠标所在的位置;在结束绘制的时候,会将攻击直箭头的起点和终点返回。在编辑的时候也是一样的。
接下来使用gui将这个库适配的所有绘制类型都尝试一下
const plotTypes = [{name: "圆形",type: "Circle",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "多边形",type: "Polygon",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "矩形",type: "Reactangle",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "三角形",type: "Triangle",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "细箭头",type: "FineArrow",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "攻击箭头",type: "AttackArrow",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "燕尾攻击箭头",type: "SwallowtailAttackArrow",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "分队战斗",type: "SquadCombat",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "燕尾分队战斗",type: "SwallowtailSquadCombat",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "直箭头",type: "StraightArrow",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "突击方向",type: "AssaultDirection",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "曲箭头",type: "CurvedArrow",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "双箭头",type: "DoubleArrow",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "自由线",type: "FreehandLine",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "曲线",type: "Curve",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "椭圆",type: "Ellipse",options:{material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "弓形",type: "Lune",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},{name: "自由多边形",type: "FreehandPolygon",options: {material: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 0.5)"),outlineMaterial: Cesium.Color.fromCssColorString("rgba(59, 178, 208, 1)"),outlineWidth: 3,},},
];let geometry = null;
const plotParams = {绘制类型: plotTypes[0].name,
};const gui = new dat.GUI();
gui.add(plotParams, "绘制类型", plotTypes.map((item) => item.name)).onChange((val) => {if (geometry) {geometry.remove();geometry = null;}const selected = plotTypes.find((item) => item.name === val);if (selected) {geometry = new CesiumPlot[selected.type](Cesium, viewer, selected.options);geometry.on("drawStart", () => {console.log("开始绘制");});geometry.on("drawUpdate", (data) => {console.log("绘制中", data);});geometry.on("drawEnd", (data) => {console.log("结束绘制", data);});geometry.on("editStart", (data) => {console.log("开始编辑", data);});geometry.on("editEnd", (data) => {console.log("编辑结束", data);});}
});
// 默认初始化第一个类型
geometry = new CesiumPlot[plotTypes[0].type](Cesium, viewer, plotTypes[0].options);
geometry.on("drawStart", () => {console.log("开始绘制");
});
geometry.on("drawUpdate", (data) => {console.log("绘制中", data);
});
geometry.on("drawEnd", (data) => {console.log("结束绘制", data);
});
geometry.on("editStart", (data) => {console.log("开始编辑", data);
});
geometry.on("editEnd", (data) => {console.log("编辑结束", data);
});
测试之后都没有任何问题