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

测试之后都没有任何问题

相关推荐
Superxpang3 小时前
前端性能优化
前端·javascript·vue.js·性能优化
左手吻左脸。3 小时前
解决el-select因为弹出层层级问题,不展示下拉选
javascript·vue.js·elementui
李白的故乡3 小时前
el-tree-select名字
javascript·vue.js·ecmascript
Rysxt_3 小时前
Element Plus 入门教程:从零开始构建 Vue 3 界面
前端·javascript·vue.js
隐含3 小时前
对于el-table中自定义表头中添加el-popover会弹出两个的解决方案,分别针对固定列和非固定列来隐藏最后一个浮框。
前端·javascript·vue.js
大鱼前端3 小时前
Turbopack vs Webpack vs Vite:前端构建工具三分天下,谁将胜出?
前端·webpack·turbopack
你的人类朋友3 小时前
先用js快速开发,后续引入ts是否是一个好的实践?
前端·javascript·后端
知识分享小能手3 小时前
微信小程序入门学习教程,从入门到精通,微信小程序核心 API 详解与案例(13)
前端·javascript·学习·react.js·微信小程序·小程序·vue