Cesium 军事标绘入门:用 Cesium-Plot-JS 快速实现标绘功能

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);

});

测试之后都没有任何问题