Cesium 无人机巡检三维效果实现

uavInspection.js代码

/**
 * 无人机巡检
 */
import * as mars3d from "mars3d"
import * as Cesium from 'mars3d-cesium'
import * as turf from "@turf/turf"
import { getMap } from '@/components/mars3dMap/js/index.js'
import { getHeightByLngLat } from '@/components/mars3dMap/js/utils.js'
import GlowLineMaterialProperty from '@/components/mars3dMap/js/material/GlowLineMaterialProperty.js'
import iamge from "@/components/mars3dMap/images/路径撒点.png";
import iamge2 from "@/components/mars3dMap/images/路径撒点2.png";
let graphicLayer;
let uavInspectionMap = new Map();
let pointCallback; // 无人机经过点位回调
let UavInspection = (function () {
 /**
 * 无人机巡检
 */
 function UavInspection(id, positions) {
 this.id = id;
 this.positions = positions || [];
 this.graphicMap = new Map();
 this.step = 20;
 this.callbackPointSet = new Set(); // 回调点位集合
 if (positions && positions.length > 0) {
 this.segmentPositions = this.divideLine(positions, this.step);
 }
 if (!graphicLayer) {
 let map = getMap();
 graphicLayer = new mars3d.layer.GraphicLayer()
 map.addLayer(graphicLayer);
 }
 }
 UavInspection.prototype.finishLabels = function (index) {
 for (let i = 1; i <= index; i++) {
 this.finishLabel(i);
 }
 }
 UavInspection.prototype.finishLabel = function (index) {
 let id = `label-${index}`;
 if (this.graphicMap.has(id)) {
 let data = this.graphicMap.get(id);
 data.graphic.setStyle({ image: iamge2 });
 }
 }
 UavInspection.prototype.addLabels = function () {
 for (let [i, pos] of this.positions.entries()) {
 let samePos = false; // 第1个点和最后一个点位置是否相同
 if (this.positions.length > 0) {
 let lastPos1 = this.positions[0];
 let lastPos2 = this.positions[this.positions.length - 1];
 let distance = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lastPos1[0], lastPos1[1], lastPos1[2]),
 Cesium.Cartesian3.fromDegrees(lastPos2[0], lastPos2[1], lastPos2[2]));
 if (distance < 5) {
 samePos = true;
 }
 }
 if (i < (samePos ? this.positions.length - 1 : this.positions.length)) {
 this.addLabel(i + 1, pos[0], pos[1], pos[2]);
 }
 }
 }
 UavInspection.prototype.addLabel = function (index, lng, lat, height) {
 let id = `label-${index}`;
 let data;
 if (this.graphicMap.has(id)) {
 return;
 } else {
 data = { id: id, type: 'label' };
 this.graphicMap.set(id, data);
 }
 data.graphic = new mars3d.graphic.BillboardEntity({
 id: id,
 position: [lng, lat, height],
 style: {
 image: iamge,
 scale: 0.5,
 horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
 verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
 clampToGround: false,
 pixelOffsetY: 30,
 label: {
 text: `${index}`,
 pixelOffsetY: -31,
 visibleDepth: false
 }
 },
 })
 graphicLayer.addGraphic(data.graphic);
 }
 UavInspection.prototype.clear = function () {
 for (let id of this.graphicMap.keys()) {
 let data = this.graphicMap.get(id);
 this.graphicMap.delete(id);
 graphicLayer.removeGraphic(data.graphic);
 data.graphic = undefined;
 if (data.type == 'drone') {
 data.positionProperty = undefined;
 }
 }
 this.callbackPointSet.clear();
 }
 UavInspection.prototype.createPath = function () {
 return { // 实时轨迹显示
 show: true,
 leadTime: 0, // 飞机将要经过的路径,路径存在的时间
 trailTime: 60, // 飞机已经经过的路径,路径存在的时间
 width: 8, // 线宽度
 resolution: 1,
 // color: 'rgba(255, 193, 37, 1)',
 /* material: new Cesium.PolylineGlowMaterialProperty({
 glowPower: 0.25, // 轨迹线的发光强度
 color: new Cesium.Color(255 / 255, 193 / 255, 37 / 255, 1) // 颜色
 }) */
 material: new GlowLineMaterialProperty({
 color: new Cesium.Color(255 / 255, 255 / 255, 0 / 255, 1),
 power: 0.2
 })
 };
 }
 UavInspection.prototype.createModel = function () {
 return new mars3d.graphic.ModelEntity({
 position: [0, 0, 0], // 默认值
 style: {
 url: 'gltf/四旋翼无人机1.glb',
 scale: 6,
 minimumPixelSize: 100,
 heading: 0
 },
 path: this.createPath()
 });
 }
 UavInspection.prototype.createDrone = function () {
 let id = this.id;
 let data;
 if (this.graphicMap.has(id)) {
 return;
 } else {
 data = { id: id, type: 'drone' };
 this.graphicMap.set(id, data);
 data.positionProperty = new Cesium.SampledPositionProperty();
 data.positionProperty.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD; // 后续时段保持末位置
 }
 if (!data.graphic) {
 data.graphic = this.createModel();
 graphicLayer.addGraphic(data.graphic);
 data.graphic.position = data.positionProperty;
 this.addDashLine();
 }
 }
 UavInspection.prototype.updateDronePosition = function (lng, lat, height) {
 let id = this.id;
 if (this.graphicMap.has(id)) {
 let data = this.graphicMap.get(id);
 if (data.graphic) {
 this.lastTime = new Date().getTime();
 data.position = [lng, lat, height];
 let position = Cesium.Cartesian3.fromDegrees(lng, lat, height);
 let delay = 2000; // 延迟(单位:毫秒)
 let time = Cesium.JulianDate.fromDate(new Date(new Date().getTime() + delay));
 data.positionProperty.addSample(time, position);
 setTimeout(() => {
 this.updateDashLine(lng, lat, height);
 this.drawBlocks(lng, lat);
 }, delay);
 }
 }
 // 无人机经纬标签时标签变色
 for (let [i, pos] of this.positions.entries()) {
 if (i < this.positions.length) {
 let distance = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lng, lat, 0), Cesium.Cartesian3.fromDegrees(pos[0], pos[1], 0));
 if (distance < 10) {
 this.finishLabels(i + 1);
 }
 }
 }
 // 指定点位回调
 for (let [i, pos] of this.positions.entries()) {
 let distance = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lng, lat, 0), Cesium.Cartesian3.fromDegrees(pos[0], pos[1], 0));
 if (distance < 10) {
 if (pointCallback && !this.callbackPointSet.has(i + 1)) {
 let map = getMap();
 const screenPoint = map.scene.cartesianToCanvasCoordinates(Cesium.Cartesian3.fromDegrees(lng, lat, height));
 this.callbackPointSet.add(i + 1);
 setTimeout(() => {
 pointCallback(i + 1, screenPoint);
 }, 100);
 }
 }
 }
 }
 UavInspection.prototype.addDashLine = function () {
 let id = `dashline-${this.id}`;
 let data;
 if (this.graphicMap.has(id)) {
 return;
 } else {
 data = { id: id, type: 'dashline' };
 this.graphicMap.set(id, data);
 }
 if (!data.graphic) {
 data.graphic = new mars3d.graphic.PolylineEntity({
 positions: undefined,
 style: {
 width: 2,
 clampToGround: false,
 materialType: mars3d.MaterialType.PolylineDash,
 materialOptions: {
 color: '#ff0000',
 dashLength: 16.0
 }
 }
 })
 graphicLayer.addGraphic(data.graphic)
 }
 }
 UavInspection.prototype.updateDashLine = function (lng, lat, height) {
 let id = `dashline-${this.id}`;
 if (this.graphicMap.has(id)) {
 let data = this.graphicMap.get(id);
 (async () => {
 let h = await getHeightByLngLat(getMap(), lng, lat);
 if (data.graphic) {
 data.graphic.setCallbackPositions([[lng, lat, height], [lng, lat, h]]);
 }
 })();
 }
 }
 /**
 * 计算正方形在三维空间中的四个顶点坐标
 */
 UavInspection.prototype.calculateSquareVertices = function (lng, lat, angle, step) {
 let centerPoint = Cesium.Cartesian3.fromDegrees(lng, lat, 0);
 // 获取中心点处的ENU坐标系基向量(东、北、天)
 const matrix = Cesium.Transforms.eastNorthUpToFixedFrame(centerPoint);
 const east = new Cesium.Cartesian3();
 const north = new Cesium.Cartesian3();
 Cesium.Matrix4.getColumn(matrix, 0, east); // 东方向单位向量
 Cesium.Matrix4.getColumn(matrix, 1, north); // 北方向单位向量
 const halfLen = step / 2;
 const angleRad = Cesium.Math.toRadians(angle); // 转为弧度
 // 1. 计算目标方向向量(θ方向)在ENU坐标系的投影分量
 const cosTheta = Math.cos(angleRad);
 const sinTheta = Math.sin(angleRad);
 // 计算主方向单位向量 (基于东和北分量)
 const dirX = cosTheta;
 const dirY = -sinTheta; // 负号用于处理顺时针旋转
 // 构造三维空间中的方向向量
 const directionVector = new Cesium.Cartesian3();
 Cesium.Cartesian3.multiplyByScalar(east, dirX, directionVector);
 Cesium.Cartesian3.add(
 directionVector,
 Cesium.Cartesian3.multiplyByScalar(north, dirY, new Cesium.Cartesian3()),
 directionVector
 );
 // 2. 计算垂直方向向量 (θ+90°) 的分量
 const perpX = -sinTheta;
 const perpY = -cosTheta;
 // 构造三维空间中的垂直向量
 const perpendicularVector = new Cesium.Cartesian3();
 Cesium.Cartesian3.multiplyByScalar(east, perpX, perpendicularVector);
 Cesium.Cartesian3.add(
 perpendicularVector,
 Cesium.Cartesian3.multiplyByScalar(north, perpY, new Cesium.Cartesian3()),
 perpendicularVector
 );
 // 3. 计算四个顶点
 const result = [];
 const scratch = new Cesium.Cartesian3();
 // 顶点计算函数 (避免重复代码)
 const calculatePoint = (xFactor, yFactor) => {
 const point = scratch;
 // point = centerPoint + (directionVector * xFactor * halfLen) + (perpendicularVector * yFactor * halfLen)
 Cesium.Cartesian3.multiplyByScalar(directionVector, xFactor * halfLen, point);
 Cesium.Cartesian3.add(
 point,
 Cesium.Cartesian3.multiplyByScalar(perpendicularVector, yFactor * halfLen, new Cesium.Cartesian3()),
 point
 );
 return Cesium.Cartesian3.add(centerPoint, point, new Cesium.Cartesian3());
 };
 // 生成4个顶点(顺时针顺序)
 result.push(calculatePoint(1, -1)); // 起点:右下
 result.push(calculatePoint(1, 1)); // 右上
 result.push(calculatePoint(-1, 1)); // 左上
 result.push(calculatePoint(-1, -1)); // 左下
 return result;
 }
 UavInspection.prototype.getSquareVertices = function (pos, pos1, pos2, step) {
 let angle = this.calculateBearingFromEast(pos1, pos2);
 let r = this.calculateSquareVertices(pos[0], pos[1], angle, step);
 let result = { center: pos, positions: [] };
 for (let i = 0; i < r.length; i++) {
 const cartographic = Cesium.Cartographic.fromCartesian(r[i]);
 let position = [Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude), cartographic.height];
 result.positions.push(position);
 }
 return result;
 }
 UavInspection.prototype.getRectangleVertices = function (pos1, pos2, step) {
 let mid1 = Cesium.Cartesian3.fromDegrees(pos1[0], pos1[1], 0);
 let mid2 = Cesium.Cartesian3.fromDegrees(pos2[0], pos2[1], 0);
 const { Cartesian3, Ellipsoid, Cartographic } = Cesium;
 const ellipsoid = Ellipsoid.WGS84;
 // 计算中点连线方向向量
 const v1 = Cartesian3.subtract(mid2, mid1, new Cartesian3());
 const distance = Cartesian3.magnitude(v1);
 Cartesian3.normalize(v1, v1);
 // 计算垂直方向向量
 const center = Cartesian3.midpoint(mid1, mid2, new Cartesian3());
 const normal = ellipsoid.geodeticSurfaceNormal(center, new Cartesian3());
 const v2 = Cartesian3.cross(normal, v1, new Cartesian3());
 Cartesian3.normalize(v2, v2);
 // 缩放垂直向量到指定长度
 const halfWidth = Cartesian3.multiplyByScalar(v2, step / 2.0, new Cartesian3());
 // 计算四个顶点的笛卡尔坐标
 const cornersCartesian = [
 Cartesian3.add(mid1, Cartesian3.negate(halfWidth, new Cartesian3()), new Cartesian3()), // SW
 Cartesian3.add(mid1, halfWidth, new Cartesian3()), // NW
 Cartesian3.add(mid2, halfWidth, new Cartesian3()), // NE
 Cartesian3.add(mid2, Cartesian3.negate(halfWidth, new Cartesian3()), new Cartesian3()) // SE
 ];
 // 转换为经纬度(弧度)坐标
 const cornersRadians = cornersCartesian.map(cartesian => {
 return Cartographic.fromCartesian(cartesian);
 });
 // 转换为度数
 return cornersRadians.map(rad => {
 return [Cesium.Math.toDegrees(rad.longitude), Cesium.Math.toDegrees(rad.latitude), rad.height];
 });
 }
 UavInspection.prototype.divideSegment = function (pos1, pos2, step) {
 let p1 = Cesium.Cartesian3.fromDegrees(pos1[0], pos1[1], 0);
 let p2 = Cesium.Cartesian3.fromDegrees(pos2[0], pos2[1], 0);
 let result = [];
 let lastPos = pos1;
 let currentPos;
 let distance = Cesium.Cartesian3.distance(p1, p2);
 let d = step;
 while (d < distance - 0.1) {
 let t = d / distance;
 const interpolatedCartesian = new Cesium.Cartesian3();
 Cesium.Cartesian3.lerp(p1, p2, t, interpolatedCartesian);
 const cartographic = Cesium.Cartographic.fromCartesian(interpolatedCartesian);
 currentPos = [Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude), cartographic.height];
 result.push({
 pos1: lastPos,
 pos2: currentPos,
 positions: this.getRectangleVertices(lastPos, currentPos, step)
 });
 lastPos = currentPos;
 d += step;
 }
 result.push({
 pos1: currentPos,
 pos2: pos2,
 positions: this.getRectangleVertices(currentPos, pos2, step)
 });
 return result;
 }
 UavInspection.prototype.divideLine = function (positions, step) {
 let result = [];
 for (let i = 0; i < positions.length - 1; i++) {
 let pos1 = positions[i];
 let pos2 = positions[i + 1];
 let array = this.divideSegment(pos1, pos2, step);
 result.push(...array);
 }
 return result.slice(0, -1);
 }
 UavInspection.prototype.drawBlocks = function (lng, lat) {
 if (this.segmentPositions && this.segmentPositions.length > 0) {
 let index = -1;
 for (let [i, pos] of this.segmentPositions.entries()) {
 let distance1 = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(pos.pos1[0], pos.pos1[1], 0), Cesium.Cartesian3.fromDegrees(pos.pos2[0], pos.pos2[1], 0));
 let distance2 = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lng, lat, 0), Cesium.Cartesian3.fromDegrees(pos.pos1[0], pos.pos1[1], 0));
 let distance3 = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lng, lat, 0), Cesium.Cartesian3.fromDegrees(pos.pos2[0], pos.pos2[1], 0));
 if (distance1 - (distance2 + distance3) > -0.1) {
 index = i;
 }
 }
 for (let i = 0; i <= index; i++) {
 let pos = this.segmentPositions[i];
 let pos2
 if (i + 1 < this.segmentPositions.length) {
 pos2 = this.segmentPositions[i + 1];
 } else {
 pos2 = this.segmentPositions[i - 1];
 }
 this.drawBlock(i, pos);
 }
 } else {
 if (!this.lastBlockPos) {
 this.lastBlockPos = [lng, lat, 0];
 this.blockIndex = 0;
 } else {
 let distance = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(this.lastBlockPos[0], this.lastBlockPos[1], 0), Cesium.Cartesian3.fromDegrees(lng, lat, 0));
 if (distance >= this.step) {
 let pos2 = [lng, lat, 0];
 let vertices = this.getRectangleVertices(this.lastBlockPos, pos2, this.step);
 this.drawBlock(this.blockIndex++, { positions: vertices });
 this.lastBlockPos = [lng, lat, 0];
 }
 }
 }
 }
 UavInspection.prototype.drawBlock = function (i, pos) {
 let id = `block-${i}`;
 let data;
 if (this.graphicMap.has(id)) {
 return;
 } else {
 data = { id: id, type: 'block' };
 this.graphicMap.set(id, data);
 }
 if (!data.graphic) {
 data.graphic = new mars3d.graphic.PolygonPrimitive({
 positions: pos.positions,
 style: {
 color: "#ff0000",
 opacity: 0.3,
 clampToGround: true
 }
 })
 graphicLayer.addGraphic(data.graphic)
 }
 }
 /**
 * 计算两点间方向(正东为0°,顺时针到360°)
 * @returns {number} 方向角度(0°到360°,正东方向为0°)
 */
 UavInspection.prototype.calculateBearingFromEast = function (pos1, pos2) {
 const point1 = Cesium.Cartographic.fromDegrees(pos1[0], pos1[1]);
 const point2 = Cesium.Cartographic.fromDegrees(pos2[0], pos2[1]);
 const lon1 = point1.longitude;
 const lat1 = point1.latitude;
 const lon2 = point2.longitude;
 const lat2 = point2.latitude;
 // 经度差(考虑跨日期变更线)
 const dLon = lon2 - lon1;
 // 计算Y分量(与正北相关)
 const y = Math.sin(dLon) * Math.cos(lat2);
 // 计算X分量(与正北相关)
 const x = Math.cos(lat1) * Math.sin(lat2) -
 Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
 // 计算初始方位角(0°为正北,顺时针)
 const bearingRad = Math.atan2(y, x);
 let bearingDeg = Cesium.Math.toDegrees(bearingRad);
 // 转换为0°~360°范围
 if (bearingDeg < 0) {
 bearingDeg += 360;
 }
 // 坐标系转换:正北0° → 正东0°
 // 转换公式: (bearingDeg + 90) % 360
 return (bearingDeg + 90) % 360;
 }
 return UavInspection;
})()
function createUavInspection(id, positions) {
 if (!uavInspectionMap.has(id)) {
 let uavInspection = new UavInspection(id, positions);
 uavInspection.createDrone();
 uavInspection.addLabels();
 uavInspectionMap.set(id, uavInspection);
 }
}
function uavInspectionExists(id) {
 return uavInspectionMap.has(id);
}
function updateUavInspectionPosition(id, lng, lat, height) {
 if (uavInspectionMap.has(id)) {
 let uavInspection = uavInspectionMap.get(id);
 uavInspection.updateDronePosition(lng, lat, height);
 }
}
function clearUavInspections() {
 for (let id of uavInspectionMap.keys()) {
 let uavInspection = uavInspectionMap.get(id);
 uavInspectionMap.delete(id);
 uavInspection.clear();
 }
 graphicLayer = undefined;
}
// 定时清理
setInterval(() => {
 for (let id of uavInspectionMap.keys()) {
 let uavInspection = uavInspectionMap.get(id);
 if (new Date().getTime() - uavInspection.lastTime > 20 * 1000) {
 uavInspectionMap.delete(id);
 uavInspection.clear();
 }
 }
}, 1000);
// 无人机经过点位回调
function registerPointCallback(callback) {
 pointCallback = callback;
}
export { uavInspectionExists, createUavInspection, updateUavInspectionPosition, clearUavInspections, registerPointCallback }

实时接收WebSocket数据,展示无人机、航线、地面色块

部分代码如下

import { uavInspectionExists, createUavInspection, updateUavInspectionPosition } from '@/components/mars3dMap/js/uavInspection.js'
async function processWebSocketData(json) {
 if (json.method && json.method == "FLIGHT" && json.data) {
 let data = json.data;
 let id = data.droneSN;
 let model = data.model; // 飞机型号
 let lng = data.longitude;
 let lat = data.latitude;
 let alt = data.altitude; // 海拔高度
 let heading = data.heading; // 朝向(度)
 let pitch = data.pitch; // 俯仰角(度)
 let roll = data.roll; // 翻转角(度)
 let groundSpeed = data.groundSpeed; // 地面速度,单位m/s
 if (!uavInspectionExists(id)) {
 let positions;
 if (id == config.droneSN) {
 positions = config.passingPoints;
 } else {
 positions = [];
 }
 createUavInspection(id, positions)
 }
 updateUavInspectionPosition(id, lng, lat, alt);
 }
}

效果截图

截图说明:由于测试笔记本比较卡,所以红虚线、红色块和无人机的位置不同步

作者:0611163原文地址:https://www.cnblogs.com/s0611163/p/18961871

%s 个评论

要回复文章请先登录注册