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 of this.positions.entries()) {
let samePos = false; // 第1个点和最后一个点位置是否相同
if (this.positions.length > 0) {
let lastPos1 = this.positions;
let lastPos2 = this.positions;
let distance = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lastPos1, lastPos1, lastPos1),
Cesium.Cartesian3.fromDegrees(lastPos2, lastPos2, lastPos2));
if (distance < 5) {
samePos = true;
}
}
if (i < (samePos ? this.positions.length - 1 : this.positions.length)) {
this.addLabel(i + 1, pos, pos, pos);
}
}
}
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: ,
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: , // 默认值
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 = ;
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 of this.positions.entries()) {
if (i < this.positions.length) {
let distance = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lng, lat, 0), Cesium.Cartesian3.fromDegrees(pos, pos, 0));
if (distance < 10) {
this.finishLabels(i + 1);
}
}
}
// 指定点位回调
for (let of this.positions.entries()) {
let distance = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lng, lat, 0), Cesium.Cartesian3.fromDegrees(pos, pos, 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([, ]);
}
})();
}
}
/**
* 计算正方形在三维空间中的四个顶点坐标
*/
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, pos, angle, step);
let result = { center: pos, positions: [] };
for (let i = 0; i < r.length; i++) {
const cartographic = Cesium.Cartographic.fromCartesian(r);
let position = ;
result.positions.push(position);
}
return result;
}
UavInspection.prototype.getRectangleVertices = function (pos1, pos2, step) {
let mid1 = Cesium.Cartesian3.fromDegrees(pos1, pos1, 0);
let mid2 = Cesium.Cartesian3.fromDegrees(pos2, pos2, 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 ;
});
}
UavInspection.prototype.divideSegment = function (pos1, pos2, step) {
let p1 = Cesium.Cartesian3.fromDegrees(pos1, pos1, 0);
let p2 = Cesium.Cartesian3.fromDegrees(pos2, pos2, 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 = ;
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;
let pos2 = positions;
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 of this.segmentPositions.entries()) {
let distance1 = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(pos.pos1, pos.pos1, 0), Cesium.Cartesian3.fromDegrees(pos.pos2, pos.pos2, 0));
let distance2 = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lng, lat, 0), Cesium.Cartesian3.fromDegrees(pos.pos1, pos.pos1, 0));
let distance3 = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(lng, lat, 0), Cesium.Cartesian3.fromDegrees(pos.pos2, pos.pos2, 0));
if (distance1 - (distance2 + distance3) > -0.1) {
index = i;
}
}
for (let i = 0; i <= index; i++) {
let pos = this.segmentPositions;
let pos2
if (i + 1 < this.segmentPositions.length) {
pos2 = this.segmentPositions;
} else {
pos2 = this.segmentPositions;
}
this.drawBlock(i, pos);
}
} else {
if (!this.lastBlockPos) {
this.lastBlockPos = ;
this.blockIndex = 0;
} else {
let distance = Cesium.Cartesian3.distance(Cesium.Cartesian3.fromDegrees(this.lastBlockPos, this.lastBlockPos, 0), Cesium.Cartesian3.fromDegrees(lng, lat, 0));
if (distance >= this.step) {
let pos2 = ;
let vertices = this.getRectangleVertices(this.lastBlockPos, pos2, this.step);
this.drawBlock(this.blockIndex++, { positions: vertices });
this.lastBlockPos = ;
}
}
}
}
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, pos1);
const point2 = Cesium.Cartographic.fromDegrees(pos2, pos2);
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);
}
}效果截图
截图说明:由于测试笔记本比较卡,所以红虚线、红色块和无人机的位置不同步
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]