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

测试之后都没有任何问题

相关推荐
爱上妖精的尾巴14 小时前
8-1 WPS JS宏 String.raw等关于字符串的3种引用方式
前端·javascript·vue.js·wps·js宏·jsa
hvang198814 小时前
某花顺隐藏了重仓涨幅,通过chrome插件计算基金的重仓涨幅
前端·javascript·chrome
Async Cipher14 小时前
TypeScript 的用法
前端·typescript
web打印社区14 小时前
vue页面打印:printjs实现与进阶方案推荐
前端·javascript·vue.js·electron·html
We་ct14 小时前
LeetCode 30. 串联所有单词的子串:从暴力到高效,滑动窗口优化详解
前端·算法·leetcode·typescript
木卫二号Coding14 小时前
Docker-构建自己的Web-Linux系统-Ubuntu:22.04
linux·前端·docker
CHU72903515 小时前
一番赏盲盒抽卡机小程序:解锁惊喜体验与社交乐趣的多元功能设计
前端·小程序·php
RFCEO15 小时前
前端编程 课程十二、:CSS 基础应用 Flex 布局
前端·css·flex 布局·css3原生自带的布局模块·flexible box·弹性盒布局·垂直居中困难
天若有情67315 小时前
XiangJsonCraft v1.2.0重大更新解读:本地配置优先+全量容错,JSON解耦开发体验再升级
前端·javascript·npm·json·xiangjsoncraft
2501_9445255415 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 预算详情页面
android·开发语言·前端·javascript·flutter·ecmascript