基于 vue+Cesium 实现军事标绘之钳击箭头绘制实战

效果图

基于 vue+Cesium 实现军事标绘之钳击箭头绘制实战_第1张图片

在地理信息系统(GIS)开发中,军事标绘是一个重要的应用场景,其中箭头类标绘(如攻击箭头、钳击箭头)是常用的战术符号。本文将基于 Cesium 引擎,详细讲解如何实现可交互的钳击箭头绘制功能,支持动态跟随鼠标调整、固定部分标绘区域及自动清理临时标记等特性。

一、技术背景与实现目标

Cesium 简介
Cesium 是一款开源的 3D 地理信息引擎,支持高精度全球地形、影像加载及矢量数据可视化,广泛应用于数字地球、军事仿真等领域。其强大的空间分析能力和实时渲染特性,使其成为军事标绘的理想选择。

实现目标
本文将实现一个交互式钳击箭头绘制工具,具备以下功能:

  1. 鼠标点击确定标绘关键点,支持动态调整
  2. 第三次点击后固定右侧箭头,左侧保持跟随鼠标移动
  3. 绘制完成后自动隐藏标记点,仅保留最终标绘图形
  4. 支持标绘数据的提取与格式化输出
二、环境准备

依赖引入
实现该功能需要以下核心依赖:

// 导入Cesium地图初始化工具
import initMap from '@/config/initMap.js';
// 地图配置(含底图服务地址)
import { mapConfig } from '@/config/mapConfig';
// 军事标绘算法库(提供箭头生成逻辑)
import xp from '@/utils/algorithm.js';
// 标记点图片资源
import boardimg from '@/assets/images/captain-01.png';

基础组件结构
我们将创建一个 Vue 组件,通过cesium-container容器承载地图实例:




三、核心实现详解
3.1 地图初始化与数据准备

在组件挂载阶段初始化 Cesium 实例,并设置绘制延迟以确保资源加载完成:

export default {
  name: 'CesiumMap',
  data() {
    return {
      viewer: null,                // Cesium核心实例
      drawHandler: null,           // 屏幕事件处理器
      layerId: 'pincerArrowLayer', // 标绘图层ID(用于实体管理)
      fixedPoints: [],             // 已固定的关键点坐标
      currentPoint: null,          // 当前鼠标位置(浮动点)
      pointEntities: []            // 标记点实体集合(用于清理)
    };
  },
  mounted() {
    // 初始化地图(使用高德底图)
    this.viewer = initMap(mapConfig.gaode.url3, false);
    
    // 延迟5秒开始绘制,确保地图加载完成
    setTimeout(() => {
      this.addPincerArrow();
    }, 5000);
  }
};
3.2 交互事件处理

通过Cesium.ScreenSpaceEventHandler监听鼠标事件,实现关键点采集与动态调整:

methods: {
  addPincerArrow() {
    // 初始化数据与清理历史标绘
    this.fixedPoints = [];
    this.currentPoint = null;
    this.pointEntities = [];
    this.clearPlot();

    // 创建箭头实体(动态更新)
    this.showRegion2Map();

    // 初始化事件处理器
    this.drawHandler = new Cesium.ScreenSpaceEventHandler(
      this.viewer.scene.canvas
    );

    // 左键点击:添加固定点
    this.drawHandler.setInputAction((event) => {
      // 屏幕坐标转地图坐标(笛卡尔坐标系)
      const position = event.position;
      const ray = this.viewer.camera.getPickRay(position);
      const cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene);
      
      if (!Cesium.defined(cartesian)) return;

      // 限制最大点数为5个
      if (this.fixedPoints.length >= 5) return;

      // 添加固定点并创建标记
      this.fixedPoints.push(cartesian);
      const pointEntity = this.createPoint(cartesian, this.fixedPoints.length - 1);
      this.pointEntities.push(pointEntity);

      // 初始化第一个浮动点
      if (this.fixedPoints.length === 1) {
        this.currentPoint = cartesian.clone();
      }

      // 达到5个点时完成绘制
      if (this.fixedPoints.length === 5) {
        this.cleanupDrawing(true); // 清理资源并隐藏标记
        this.getPincerArrowValue(); // 提取标绘数据
      }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    // 鼠标移动:更新浮动点
    this.drawHandler.setInputAction((event) => {
      // 仅在未完成绘制时更新
      if (this.fixedPoints.length === 0 || this.fixedPoints.length >= 5) return;

      // 计算当前鼠标的地图坐标
      const position = event.endPosition;
      const ray = this.viewer.camera.getPickRay(position);
      const cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene);
      
      if (Cesium.defined(cartesian)) {
        this.currentPoint = cartesian;
        this.viewer.scene.requestRender(); // 触发重绘
      }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  }
}
3.3 动态箭头绘制核心逻辑

通过Cesium.CallbackProperty实现箭头的实时更新,区分固定区域与动态区域:

showRegion2Map() {
  // 定义箭头样式(填充色与轮廓)
  const fillMaterial = Cesium.Color.fromCssColorString('#ff0').withAlpha(0.5);
  const outlineMaterial = new Cesium.PolylineDashMaterialProperty({
    dashLength: 16,
    color: Cesium.Color.fromCssColorString('#f00').withAlpha(0.7)
  });

  // 动态计算箭头多边形
  const dynamicHierarchy = new Cesium.CallbackProperty(() => {
    // 至少需要3个点才能绘制箭头
    if (this.fixedPoints.length < 3) return null;

    try {
      // 构建点集:前3个固定点 + 动态点
      let positions;
      if (this.fixedPoints.length >= 3) {
        // 前3个点固定,后续点动态更新
        const fixedPart = this.fixedPoints.slice(0, 3);
        let floatingPart = this.fixedPoints.slice(3);

        // 添加当前鼠标位置(浮动点)
        if (this.currentPoint && this.fixedPoints.length < 5) {
          floatingPart.push(this.currentPoint);
        }

        positions = [...fixedPart, ...floatingPart];
      }

      // 坐标转换:笛卡尔坐标转经纬度
      const lonLats = this.getLonLatArr(positions);
      this.removeDuplicate(lonLats); // 去重处理

      // 调用算法生成箭头多边形
      const doubleArrow = xp.algorithm.doubleArrow(lonLats);
      if (!doubleArrow || !doubleArrow.polygonalPoint) return null;

      // 返回多边形层级数据
      const pHierarchy = new Cesium.PolygonHierarchy(doubleArrow.polygonalPoint);
      pHierarchy.keyPoints = lonLats;
      return pHierarchy;
    } catch (err) {
      console.error('箭头计算失败:', err);
      return null;
    }
  }, false);

  // 创建箭头实体(填充+轮廓)
  const entity = this.viewer.entities.add({
    polygon: new Cesium.PolygonGraphics({
      hierarchy: dynamicHierarchy,
      material: fillMaterial,
      show: true
    }),
    polyline: new Cesium.PolylineGraphics({
      positions: new Cesium.CallbackProperty(() => {
        // 与多边形逻辑类似,生成轮廓线
        // ...(省略与dynamicHierarchy类似的轮廓计算逻辑)
      }, false),
      clampToGround: true,
      width: 2,
      material: outlineMaterial
    })
  });

  entity.layerId = this.layerId;
  entity.valueFlag = 'value';
}
3.4 辅助功能实现

标记点管理
创建临时标记点并在绘制完成后清理:

// 创建标记点
createPoint(cartesian, oid) {
  return this.viewer.entities.add({
    position: cartesian,
    billboard: {
      image: boardimg,
      scale: 1.0,
      width: 32,
      height: 32
    },
    oid, // 自定义属性:点编号
    layerId: this.layerId,
    flag: 'keypoint' // 标记点类型
  });
}

// 清理资源
cleanupDrawing(isComplete) {
  // 销毁事件处理器
  if (this.drawHandler) {
    this.drawHandler.destroy();
    this.drawHandler = null;
  }

  // 绘制完成后移除所有标记点
  if (isComplete) {
    this.pointEntities.forEach(entity => {
      this.viewer.entities.remove(entity);
    });
  }
}

坐标转换与数据提取
将 Cesium 内部坐标转换为通用经纬度格式:

// 笛卡尔坐标转经纬度
getLonLat(cartesian) {
  const cartographic = this.viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian);
  return {
    lon: Cesium.Math.toDegrees(cartographic.longitude),
    lat: Cesium.Math.toDegrees(cartographic.latitude)
  };
}

// 提取标绘数据
getPincerArrowValue() {
  const entityList = this.viewer.entities.values;
  for (const entity of entityList) {
    if (entity.valueFlag === 'value') {
      const hierarchy = entity.polygon.hierarchy.getValue();
      if (!hierarchy || !hierarchy.positions) continue;

      // 转换为经纬度数组
      const coordinates = hierarchy.positions.map(pos => {
        const cartographic = this.viewer.scene.globe.ellipsoid.cartesianToCartographic(pos);
        return {
          lat: Cesium.Math.toDegrees(cartographic.latitude),
          lng: Cesium.Math.toDegrees(cartographic.longitude)
        };
      });

      console.log('钳击箭头数据:', coordinates);
      console.log('关键点:', hierarchy.keyPoints);
    }
  }
}
四、关键技术点解析
  1. 动态更新机制
    通过Cesium.CallbackProperty实现图形实时刷新,该接口会在每一帧渲染前重新计算属性值,确保箭头随鼠标动态变化。

  2. 坐标系统转换
    Cesium 内部使用笛卡尔坐标系(Cartesian3),需通过ellipsoid.cartesianToCartographic转换为经纬度坐标,便于实际应用。

  3. 事件管理
    使用ScreenSpaceEventHandler处理鼠标交互,注意在组件销毁前调用destroy()方法释放资源,避免内存泄漏。

  4. 分层设计
    通过layerIdflag属性对实体进行分类管理,便于批量清理和查询。

五、总结与扩展

本文实现了一个具备动态交互能力的军事标绘工具,核心是通过 Cesium 的事件系统与动态属性实现标绘图形的实时更新,并通过分层数据管理实现固定区域与动态区域的分离。

扩展方向

  1. 支持更多标绘符号(如进攻箭头、集结地等)
  2. 添加标绘编辑功能(移动、删除关键点)
  3. 实现标绘数据的持久化存储与加载
  4. 优化大场景下的渲染性能
六、完整代码(需要导入算法的请留言)





 

你可能感兴趣的:(基于 vue+Cesium 实现军事标绘之钳击箭头绘制实战)