Ceisum无人机巡检直播视频投射

接上次的视频投影,Leader告诉我这个视频投影要用在两个地方,一个是我原先写的轨迹回放那里,另一个在无人机起飞后的地图回显,要实时播放无人机拍摄的视频,还要能转镜头,让我把这个也接一下。
我的天!告诉我的时候人都傻了,这是一个功能嘛?
一个是拿到了全部的轨迹数据进行回显,播放的视频也是完整的资源,视频要求投射在地面上。另一个是接收实时的轨迹数据进行回显,播放的是实时的直播,视频居然还要求跟着镜头一起转。
这是两个完完全全不一样的功能好吧!!!
我拿着标书仔仔细细看过那行“演示无人机画面投影到地图上”,一时间陷入了沉默。
无语归无语,但特么还是要写,跟Leader据理力争这不是改改就能换上的功能,然后争取来两周的研发时间。
两周时间看起来很长,其实也就10天,有时候我写个计算方法都要两天,还不一定是最终版,所以只能拜托接下来运气很好,别让我遇到太多阻碍。
开工吧!

Step 0:
思路:最初都是想要拿原来的方法改改看,哪怕不成功,也绝了这个念想。
首先要解决的是视频投放的问题,原本这个投影方法只能投放视频,且只能投影到地面,我要优化成能投影直播并且不接触地面也能投放。
在这里插入图片描述
为了解决直播投放问题,我尝试修改了源码,发现困难重重,主要是两点,一个是原方法不依赖dom元素,这意味着我不能暴力篡改成直播通道,另一个是封装层级过多,我想在某几个关键步骤看下效果都不能被满足,只能盲写看最终效果。
卡在第一步我是万万没有想到,经过一下午的尝试,最终让我放弃了继续下去的想法。

Step0.1:当然,我也没有急着完全放弃之前的代码,我还记得我czml的无人机轨迹方法已经很完善了,从静态路径加载改为动态路径回显似乎并不难,如果成功的话只需要给视频材质连四根线,接着解决朝向转动的问题就完成了,大大减轻了工作量。

在这里插入图片描述
我将原有轨迹数据做成坐标发射器,通过动态接收点位组成路径,每次接收到数据就更新czml加载的点位集合,成功改成了动态路径回显。
然后我尝试将我之前做好的视椎体视频替换进去,然后切换朝向,发现在dataSource.then中格外难操作矩阵,需要多考虑很多问题,于是暂时搁置该方法,等待重启机会。

旧路已死,新步骤开始从0开始一点点搭建功能。

Step 1:
思路: 选择实体结合时间轴,更为灵活地构建动画,同时代码将更繁琐。
首先写一段简单动画,从一个点到另一个点。
在这里插入图片描述

部分代码:

var startPosition = Cesium.Cartesian3.fromDegrees(-75.0, 40.0, 1000.0);
var endPosition = Cesium.Cartesian3.fromDegrees(-75.0, 42.0, 1000.0);

// 设置时间范围
var startTime = Cesium.JulianDate.now();
var endTime = Cesium.JulianDate.addSeconds(
  startTime,
  15,
  new Cesium.JulianDate()
);

// 创建一个 SampledPositionProperty 来定义路径
var positionProperty = new Cesium.SampledPositionProperty();

// 添加时间和位置样本
positionProperty.addSample(startTime, startPosition);
positionProperty.addSample(endTime, endPosition);

// 创建一个 entity 并设置其 position 为定义的路径
var entity = viewer.entities.add({
  position: positionProperty,
  point: {
    pixelSize: 10,
    color: Cesium.Color.RED,
  },
});

// 使视图跟踪 entity
viewer.trackedEntity = entity;

// 设置时钟范围
viewer.clock.startTime = startTime.clone();
viewer.clock.stopTime = endTime.clone();
viewer.clock.currentTime = startTime.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 当到达结束时间时停止
viewer.clock.multiplier = 1; // 时间加速倍率
viewer.clock.clockRange = Cesium.ClockRange.CLAMPED;
viewer.clock.shouldAnimate = true;

Step 2:
思路:两个点变多个点,写一个点位生成器,模拟实时接收数据,实现多点位连续飞行
在这里插入图片描述

Step 3:
思路:模拟点位飞行,点换成模型,视椎体跟随模型一起,并封装成class,效果很好,但视角连贯性有待提高
在这里插入图片描述
部分代码:

class PointMover {
  constructor(viewer) {
    this.viewer = viewer;
    this.pointQueue = [];
    this.isAnimating = false;
    this.videoEntity = null;

    // 添加模型
    this.entity = ...

    // 添加视椎体
    this.frustumPrimitive = viewer.scene.primitives.add(
      new Cesium.Primitive({
        geometryInstances: new Cesium.GeometryInstance({
          geometry: geo,
          attributes: {
            color: Cesium.ColorGeometryInstanceAttribute.fromColor(
              Cesium.Color.RED.withAlpha(0.5)
            ),
          },
        }),
        appearance: new Cesium.PerInstanceColorAppearance({
          translucent: true,
          flat: true,
        }),
        asynchronous: false,
      })
    );
    // 初始化视频材质
    this.videoEntity = viewer.entities.add({
      name: "uav-tmp-fly-wxsimple",
      polygon: {
        hierarchy: new Cesium.CallbackProperty((time) => {
          // 添加安全检查
          if (!this.entity || !this.entity.position) {
            return null;
          }

          try {
            const position = this.entity.position.getValue(time);
            if (!Cesium.defined(position)) {
              return null;
            }

            // 计算视锥体的远截面四个角点

            // 返回新的多边形层次结构
            return new Cesium.PolygonHierarchy([
              upRightPt,
              upLeftPt,
              downLeftPt,
              downRightPt,
            ]);
          } catch (error) {
            console.warn("Error calculating polygon positions:", error);
            return null;
          }
        }, false),
        perPositionHeight: true,
        material: videoElement,
        // 添加其他属性以提高渲染性能
        shadows: Cesium.ShadowMode.DISABLED,
        classificationType: Cesium.ClassificationType.BOTH,
        zIndex: 999,
      },
    });
    // 内部方法:启动动画
    startAnimation() {
      if (this.pointQueue.length > 2 && !this.isAnimating) {
        // 在 onTick 事件处理函数中更新视锥体位置
        const onTick = (clock) => {
          // 移除旧的视锥体
          // 添加新的视锥体

          // ...
        }
        // 添加 onTick 事件监听器
        this.viewer.clock.onTick.addEventListener(onTick);
      }
    }
    // 外部调用方法:更新点位并启动动画
    updatePositionsAndAnimate(newPosition) {
      this.pointQueue.push(newPosition);
      if (!this.isAnimating) {
        this.startAnimation();
      }
    }
  }
}
          

Step 4:
思路:加入虚线路径
在这里插入图片描述
部分代码:

class PointMover {
  constructor(viewer) {
    this.viewer = viewer;
    this.pointQueue = [];
    this.isAnimating = false;
    this.videoEntity = null;
    this.pathPoints = []; // 用于记录路径点
    this.pathPolyline = null; // 用于绘制路径

    // ...
  }
  // 绘制路径的方法
  createPathPolyline() {
    this.pathPolyline = this.viewer.entities.add({
      polyline: {
        positions: new Cesium.CallbackProperty((time) => {
          // 取当前记录的路径点
          return this.pathPoints;
        }, false),
        material: new Cesium.PolylineDashMaterialProperty({
          dashLength: 16.0, // 虚线的长度
        }),
        width: 3,
      },
    });
  }

  // 更新路径并添加新的位置
  updatePath(newPosition) {
    this.pathPoints.push(newPosition); // 将新位置加入到路径点

    // 更新虚线的路径
    if (this.pathPolyline) {
      this.pathPolyline.polyline.positions = new Cesium.CallbackProperty(
        (time) => {
          return this.pathPoints;
        },
        false
      );
    }
  }
  // 内部方法:启动动画
  startAnimation() {
    if (this.pointQueue.length > 2 && !this.isAnimating) {
      // 在 onTick 事件处理函数中更新视锥体位置
      const onTick = (clock) => {
       // 将当前位置添加到路径数组
       this.updatePath(currentPosition);

       //...
      }
    }
  }
}

Step 4延伸:
思路:在Step 4基础上尝试扩展,显隐,销毁 show的配合
在这里插入图片描述
部分代码:

class PointMover {
  constructor(viewer, show) {
    this.viewer = viewer;
    this.pointQueue = [];
    this.isAnimating = false;
    this.videoEntity = null;
    this.pathPoints = [];
    this.pathPolyline = null; 
    this.isFrustumShow = show; //用于显隐控制

    // ...

    if (this.frustumPrimitive) {
      if (!this.isFrustumShow) {
        this.frustumPrimitive.show = false;
      } else {
        this.frustumPrimitive.show = true;
      }
    }
    if (this.videoEntity) {
      if (!this.isFrustumShow) {
        this.videoEntity.show = false;
      } else {
        this.videoEntity.show = true;
      }
    }
    
  }
  // 内部方法:启动动画
  startAnimation() {
    if (this.pointQueue.length > 2 && !this.isAnimating) {
      // 在 onTick 事件处理函数中更新视锥体位置
      const onTick = (clock) => {
       // 将当前位置添加到路径数组

       //...
       this.frustumPrimitive = this.viewer.scene.primitives.add(
          new Cesium.Primitive({
            geometryInstances: new Cesium.GeometryInstance({
              geometry: newGeometry,
              attributes: {
                color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                  Cesium.Color.RED.withAlpha(0.5)
                ),
              },
            }),
            appearance: new Cesium.PerInstanceColorAppearance({
              translucent: true,
              flat: true,
            }),
            asynchronous: false,
          })
        );
        if (this.frustumPrimitive) {
          if (!this.isFrustumShow) {
            this.frustumPrimitive.show = false;
          } else {
            this.frustumPrimitive.show = true;
          }
        }
        if (this.videoEntity) {
          if (!this.isFrustumShow) {
            this.videoEntity.show = false;
          } else {
            this.videoEntity.show = true;
          }
        }
      }
    }
  }
  // ...
  // 外部调用方法,更新显隐开关
  updateVisibleAndHidden(val) {
    this.isFrustumShow = val;
  }
  dispose() {
    if (this.frustumPrimitive) {
      viewer.scene.primitives.remove(this.frustumPrimitive);
      this.frustumPrimitive = null;
    }
    if (this.videoEntity) {
      viewer.entities.remove(this.videoEntity);
      this.videoEntity = null;
    }
    if (this.entity) {
      viewer.entities.remove(this.entity);
      this.entity = null;
    }
    if (this.pathPolyline) {
      viewer.entities.remove(this.pathPolyline);
      this.pathPolyline = null;
    }
  }
}
if (this.pointMover) {
  clearInterval(this.intervalId);
  this.intervalId = null;
  this.pointMover.dispose();
  this.pointMover = null;
}
this.pointMover = new PointMover(viewer, this.isFrustumShow);

Step 5:
思路:尝试改变视椎体朝向
在这里插入图片描述
部分代码:

class PointMover {
  constructor(viewer) {
    // ...
    this.currentHeading = 0;
    this.currentPitch = 0;
    this.currentRoll = 0;

    // ...
  }
 
  // 内部方法:启动动画
  startAnimation() {
    if (this.pointQueue.length > 2 && !this.isAnimating) {
      // 在 onTick 事件处理函数中更新视锥体位置
      const onTick = (clock) => {
        const heading = Cesium.Math.toRadians(this.currentHeading); // 偏航角
        const pitch = Cesium.Math.toRadians(this.currentPitch); // 俯仰角
        const roll = Cesium.Math.toRadians(this.currentRoll); // 翻滚角

        // 创建一个HeadingPitchRoll对象
        const headingPitchRoll = new Cesium.HeadingPitchRoll(
          heading,
          pitch,
          roll
        );

        // 创建一个旋转矩阵
        const rotationMatrix =
          Cesium.Transforms.headingPitchRollToFixedFrame(
            currentPosition,
            headingPitchRoll,
            Cesium.Ellipsoid.WGS84
          );

        // 从旋转矩阵计算四元数
        const orientation =
          Cesium.Quaternion.fromRotationMatrix(rotationMatrix);

        //...
      }
    }
  }
  // ...
  updateHeadingPitchRoll(heading, patch, roll) {
    this.currentHeading = heading;
    this.currentPitch = patch;
    this.currentRoll = roll;
  }
}

Step 5延伸:
思路:在Step 5基础上尝试,同样矩阵转动视频材质
在这里插入图片描述
发现视频材质无法贴合视椎体,方案终止

Step 6:
思路:换用另一种视椎体构建方法,采用相机视角重置视椎体视角的方法,消除地理位置的影响
在这里插入图片描述
部分代码:

class PointMover {
  constructor(viewer) {
    // ...
    this.videoEntity = viewer.entities.add({
      name: "uav-tmp-fly-wxsimple",
      polygon: {
        hierarchy: new Cesium.CallbackProperty((time) => {
          if (!this.entity || !this.entity.position) {
            return null;
          }

          try {
            const position = this.entity.position.getValue(time);
            if (!Cesium.defined(position)) {
              return null;
            }

            let frustum = this.camera.frustum;
            let Cartesian3 = Cesium.Cartesian3;
            let camera = this.camera;

            // ...

            return new Cesium.PolygonHierarchy([
              upRightPt,
              upLeftPt,
              downLeftPt,
              downRightPt,
            ]);
          } catch (error) {
            console.warn("Error calculating polygon positions:", error);
            return null;
          }
        }, false),
        perPositionHeight: true,
        material: new Cesium.ImageMaterialProperty({
          image: videoElement, // 这里传入视频元素
          transparent: true, // 设置透明
          repeat: new Cesium.Cartesian2(1.0, 1.0), // 控制重复
        }),
        shadows: Cesium.ShadowMode.DISABLED,
        classificationType: Cesium.ClassificationType.BOTH,
        zIndex: 999,
      },
    });
  }
 
  // 内部方法:启动动画
  startAnimation() {
    if (this.pointQueue.length > 2 && !this.isAnimating) {
      // 在 onTick 事件处理函数中更新视锥体位置
      const onTick = (clock) => {
        var scene = this.viewer.scene;  
        this.camera = new Cesium.Camera(scene)

        //...
      }
    }
  }
  // ...
}

最终
接入实际项目,无人机航拍镜头实时同步反显,完结撒花°˖✧◝(⁰▿⁰)◜✧˖°
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/958585.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI Agent:深度解析与未来展望

一、AI Agent的前世:从概念到萌芽 (一)早期探索 AI Agent的概念可以追溯到20世纪50年代,早期的AI研究主要集中在简单的规则系统上,这些系统的行为是确定性的,输出由输入决定。随着时间的推移,…

Spring MVC:HTTP 请求的参数传递2.0

本篇博客接上文: Spring MVC:Spring 前置知识 & HTTP 请求的参数传递1.0-CSDN博客 目录 1. 传递 json - RequestBody 1.1 json 1.1.1 什么是 json 1.1.2 json 的语法 1.1.3 json 和 Java 中对象的转换 1.1.4 json 优点 1.2 传递 json 2. 获取路径参数 -…

电子应用设计方案103:智能家庭AI浴缸系统设计

智能家庭 AI 浴缸系统设计 一、引言 智能家庭 AI 浴缸系统旨在为用户提供更加舒适、便捷和个性化的沐浴体验,融合了人工智能技术和先进的水疗功能。 二、系统概述 1. 系统目标 - 实现水温、水位和水流的精确控制。 - 提供多种按摩模式和水疗功能。 - 具备智能清洁…

设计模式的艺术-外观模式

结构性模式的名称、定义、学习难度和使用频率如下表所示: 1.如何理解外观模式 外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。 外观模式(Facade Pattern)&a…

“““【运用 R 语言里的“predict”函数针对 Cox 模型展开新数据的预测以及推理。】“““

主题与背景 本文主要介绍了如何在R语言中使用predict函数对已拟合的Cox比例风险模型进行新数据的预测和推理。Cox模型是一种常用的生存分析方法,用于评估多个因素对事件发生时间的影响。文章通过具体的代码示例展示了如何使用predict函数的不同参数来获取生存概率和…

戴尔电脑设置u盘启动_戴尔电脑设置u盘启动多种方法

最近有很多网友问,戴尔台式机怎么设置u盘启动,特别是近两年的戴尔台式机比较复杂,有些网友不知道怎么设置,其实设置u盘启动有两种方法,下面小编教大家戴尔电脑设置u盘启动方法。 戴尔电脑设置u盘启动方法一、戴尔进入b…

【React】 react路由

这一篇文章的重点在于将React关于路由的问题都给搞清楚。 一个路由就是一个映射关系,key:value。key是路径,value 可能是function或者component。 安装react-router-dom包使用路由服务,我这里想要用的是6版本的包,因此后面加”6&q…

隐私保护+性能优化,RyTuneX 让你的电脑更快更安全

RyTuneX 是一款专为 Windows 10 和 11 用户量身打造的系统优化工具,采用先进的 WinUI 3 框架开发,以其现代化的设计风格和强大的功能集合脱颖而出。这款工具不仅界面简洁美观,还提供了多样化的系统优化选项,旨在帮助用户最大化设备…

HTML新春烟花

系列文章 序号目录1HTML满屏跳动的爱心(可写字)2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心(简易版)7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心(双心版)1…

(回溯分割)leetcode93 复原IP地址

#include<iostream> #include<vector> #include<string> #include<algorithm> using namespace std; //卡尔的图不是按照程序执行过程而是直接画程序会执行的过程 // 实际执行是&#xff1a;n个字符&#xff0c;递推n1后&#xff08;叶子节点&#xff…

关于ARM和汇编语言

一图流 ARM 计算机组成 输入设备 输出设备 存储设备 运算器 控制器 处理器读取内存程序执行的过程 取指阶段&#xff1a;控制器器通过地址总线向存储器发送想要获取的指令的地址编号&#xff0c;存储器将指定的指令发送给处理器 译码阶段&#xff1a;控制器对指令进行分…

个人学习 - 什么是Vim?

观我往旧&#xff0c;同我仰春 - 2025.1.10 声明 仅作为个人学习使用&#xff0c;仅供参考 本文所有解释参考笔者个人理解&#xff0c;最终目的是服务于自我学习&#xff0c; 如果你需要了解官方更规范的解释&#xff0c;请自行查阅 Vim 是什么 Vim 是一个强大的 文本编辑器…

python学习笔记2-简单数据类型

不同类型的变量可以进⾏的运算是不同的&#xff0c;所以必须理解变量的类型&#xff0c;python中数据类型可以分为&#xff1a; Number&#xff08;数值&#xff09; 整型&#xff08;int&#xff09; python3中只有int⼀种&#xff0c;可以表⽰整数&#xff0c;例如&#xf…

ChromeOS 132 版本更新

ChromeOS 132 版本更新 1. 企业定制化 Chrome Web Store 管理员现在可以使用新设置定制 Chrome Web Store 以适应他们管理的用户&#xff0c;包括以下功能&#xff1a; 添加公司标志添加首页横幅和自定义公告策划扩展集合实施基于类别的控制 这些设置可以通过管理员控制台进…

Python的进程和线程

ref 接受几个设定: 进程是一家almost密不透风的公司,缅甸KK园区 线程里面工作的…人 进程**[园区]**内公共资源对于进程来说,可以共享. 别的园区[进程],一般不能和自己的园区共享人员资源,除非… 好的,现在再接受设定: 单个CPU在任一时刻只能执行单个线程&#xff0c;只有…

03垃圾回收篇(D4_彻底理解GC)

目录 一、浅析大促备战过程中出现的 fullGc&#xff0c;我们能做什么&#xff1f; 1. 什么是 JVM 的 GC? 2. 写代码的时候能做什么&#xff1f; 3. 测试能做啥 4. 知识小结 二、MinorGC、MajorGC、FullGC垃圾回收介绍 1. MinorGC &#xff08;新生代垃圾回收&#xff09…

软件测试 —— jmeter(2)

软件测试 —— jmeter&#xff08;2&#xff09; HTTP默认请求头&#xff08;元件&#xff09;元件作用域和取样器作用域HTTP Cookie管理器同步定时器jmeter插件梯度压测线程组&#xff08;Stepping Thread Group&#xff09;参数解析总结 Response Times over TimeActive Thre…

分子动力学模拟里的术语:leap-frog蛙跳算法和‌Velocity-Verlet算法

分子动力学模拟&#xff08;Molecular Dynamics Simulation&#xff0c;简称MD&#xff09;是一种基于经典力学原理的计算物理方法&#xff0c;用于模拟原子和分子在给定时间内的运动和相互作用‌。以下是关于分子动力学模拟的一些核心术语和概念&#xff1a; ‌定义系统‌&am…

在 Windows 11 中为 SMB 3.x 文件共享协议提供 RDMA 支持

注&#xff1a;机翻&#xff0c;未校。 Enable SMB Direct in Windows 11 在 Windows 11 中启用 SMB Direct Provides RDMA support for the SMB 3.x file sharing protocol 为 SMB 3.x 文件共享协议提供 RDMA 支持 Vigneshwaran Vijayakumar November 3, 2024 Last Updat…

zabbix6.0安装及常用监控配置

文章目录 部署zabbix-serverzabbix监控节点部署解决zabbix中文乱码创建主机组创建模版配置主机与模版关联 监控boot分区监控网卡流量出网卡流量监控进入和出的总流量监控内存监控服务器端口用户自定应监控key值 (监控mysql查询数量)zabbix触发器监控cpu监控入网卡流量 邮件告警…