当前位置: 首页 > news >正文

数字孪生项目中实时数据驱动多节相连车厢沿轨道运行

数字孪生项目中实时数据驱动多节相连车厢沿轨道运行

用户希望在数字孪生应用中真实的还原运输列车在轨道上的位置信息,模型制作动画,数据驱动动画的方式显然是不行了,动画毕竟是一个整体的,很难通过数据精准控制。

第一步:拆分列车

整个列车需要拆分成单节的,如果不拆分的话,拐弯的位置就会斜出轨道,大概就是下图的样子。

所以需要每一节车厢都是一个独立的对象,分别作控制。

第二步:数据问题

大部分客户能给到的数据还是通过uwb基站的定位数据,既目标距离基站的距离,比如通过socket不断的推送车头距离基站的位置给你,你在应用中计算并移动。

第三步:怎么让列车在轨道上运行

在之前的文章中,我分享了人员定位的解决方案,里面提到了导航片和寻路算法,这里不赘述了,有需要的同学可以翻阅之前的文章,轨道路线是固定的路线,为了方便后面的计算,我们提取路网线段(三维点坐标集合)并进行全局存储,在这里需要注意一下,我们从路网上提出来的点,会比较稀,点与点之间的间隔或大或小,列车移动给到的数据通常比较精细,误差起码要控制在小数点后一位,为了满足这个要求,我们需要增加路网的点集合密度

// 填充路径点,增加路径点的密度
const fillPath = (path, density) => {const filledPath = [];for (let i = 0; i < path.length - 1; i++) {filledPath.push(path[i]); // 将当前点加入填充后的路径const currentPoint = path[i];const nextPoint = path[i + 1];// 计算两点之间的步长const step = [(nextPoint[0] - currentPoint[0]) / density,(nextPoint[1] - currentPoint[1]) / density,(nextPoint[2] - currentPoint[2]) / density];// 填充两点之间的中间点for (let j = 1; j < density; j++) {filledPath.push([currentPoint[0] + step[0] * j,currentPoint[1] + step[1] * j,currentPoint[2] + step[2] * j]);}}filledPath.push(path[path.length - 1]); // 添加最后一个点return filledPath;
}

在这里我们传入路径及密度参数

window.ROAD_INFO = fillPath(window.ROAD_INFO, 100)

第三步:计算每节车厢的移动路径

因为数据能给到的是头车距离基站的位置,我们假设有N节车厢,头车距离基站50米,那么后面每节车厢就是头车位置减去车身的距离

const distances = [0, 4, 8.5, 12.5, 16]; // 距离头车的偏移量

先计算每节车厢在路径上的的终点位置

//传入距离基站的位置
export const calculationPosition = (value) => {//换算在其方向上的百分比const proccess = Math.abs(value) / window.roadLength;const midpoint = getMidpointCoordinates(window.ROAD_INFO, proccess);return midpoint
}
//根据百分比计算终点位置
const getMidpointCoordinates = (coordinates, proccess) => {const totalLength = calculateLineLength(coordinates);const targetLength = totalLength * proccess;let currentLength = 0;let midpoint;for (let i = 1; i < coordinates.length; i++) {const prevPoint = coordinates[i - 1];const currentPoint = coordinates[i];const segmentLength = Math.sqrt(Math.pow(currentPoint[0] - prevPoint[0], 2) +Math.pow(currentPoint[1] - prevPoint[1], 2) +Math.pow(currentPoint[2] - prevPoint[2], 2));// 如果当前累加的长度超过50%,则获取该点的坐标if (currentLength + segmentLength >= targetLength) {const remainingLength = targetLength - currentLength;const ratio = remainingLength / segmentLength;midpoint = [prevPoint[0] + ratio * (currentPoint[0] - prevPoint[0]),prevPoint[1] + ratio * (currentPoint[1] - prevPoint[1]),prevPoint[2] + ratio * (currentPoint[2] - prevPoint[2])];break;}currentLength += segmentLength;}return midpoint;
};

在计算每节车厢从当前位置到终点位置的移动路径

// 找到给定两点之间路径上的所有点
const findPointsBetween = (path, point1, point2) => {const [nearestPoint1, nearestPoint2] = findNearestPointsOnPath(path, point1, point2);if (!nearestPoint1 || !nearestPoint2) {return []; // 如果最近点为 null,则返回空数组表示给定的两个点不在路径上}const index1 = path.indexOf(nearestPoint1);const index2 = path.indexOf(nearestPoint2);// 根据点 point1 和 point2 在路径中的位置决定起始和结束位置const startIndex = Math.min(index1, index2);const endIndex = Math.max(index1, index2);// 根据点 point1 和 point2 在路径中的位置,确定返回的路径方向const direction = index1 < index2 ? 1 : -1;// 根据方向确定返回的路径const points = path.slice(startIndex, endIndex + 1);return direction === 1 ? points : points.reverse();
}
// 找到给定两点在路径上最近的点
const findNearestPointsOnPath = (path, point1, point2) => {const nearestPoint1 = findNearestPointOnPath(path, point1);const nearestPoint2 = findNearestPointOnPath(path, point2);return [nearestPoint1, nearestPoint2];
}
// 找到路径上距离给定点最近的点
const findNearestPointOnPath = (path, point) => {let minDistance = Infinity;let nearestPoint = null;for (const pathPoint of path) {const distance = distanceBetweenPoints(pathPoint, point);if (distance < minDistance) {minDistance = distance;nearestPoint = pathPoint;}}return nearestPoint;
}
// 计算两个点之间的距离
const distanceBetweenPoints = (point1, point2) => {const [x1, y1, z1] = point1;const [x2, y2, z2] = point2;return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2) + Math.pow(z2 - z1, 2));
}

最后再让每节车厢按照自己的轨迹进行移动,就能达到视频中的效果啦,用户每隔500毫秒到1秒推送一次数据,火车平滑的在轨道上运行。

需要注意的是小车车头需要始终朝着轨道方向,在这里需要计算小车与线段之间的向量夹角,调整小车的angle,在这一章我们不做赘述,感兴趣的小伙伴可以留言,人多的话,单独出一篇文章介绍。

相关文章:

  • SEO 的未来:GPT 和 AI 如何改变关键词研究
  • 蓝桥杯刷题计划-洛谷-持续更新
  • 价格才不是小米汽车的最大“杀器”
  • 阿里云对象存储OSS入门
  • JavaSE day14笔记
  • vscode添加gitee
  • 贪心算法相关题目
  • Stable Diffusion XL之使用Stable Diffusion XL训练自己的AI绘画模型
  • 运用开关量信号远程传输装置实现工厂智能化技改需要分几步走
  • vue基础——java程序员版(总集)
  • 【python】数据库操作
  • Java File类(文件操作类)
  • 【Linux】Centos7安装redis
  • 【教程】高效数据加密混淆方法及实现简介
  • 隐私计算实训营学习四:SecretFlow的安装和部署
  • Angular Elements 及其运作原理
  • Bytom交易说明(账户管理模式)
  • codis proxy处理流程
  • co模块的前端实现
  • Date型的使用
  • Git初体验
  • JavaScript异步流程控制的前世今生
  • JSONP原理
  • Python爬虫--- 1.3 BS4库的解析器
  • yii2权限控制rbac之rule详细讲解
  • 从 Android Sample ApiDemos 中学习 android.animation API 的用法
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 试着探索高并发下的系统架构面貌
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • Prometheus VS InfluxDB
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • $(function(){})与(function($){....})(jQuery)的区别
  • (23)Linux的软硬连接
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)讲解
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (附源码)计算机毕业设计大学生兼职系统
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (全部习题答案)研究生英语读写教程基础级教师用书PDF|| 研究生英语读写教程提高级教师用书PDF
  • (四)linux文件内容查看
  • (四)图像的%2线性拉伸
  • (转)http协议
  • (转)菜鸟学数据库(三)——存储过程
  • (转载)虚函数剖析
  • .equals()到底是什么意思?
  • .NET MVC之AOP
  • /dev/VolGroup00/LogVol00:unexpected inconsistency;run fsck manually
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [ 渗透测试面试篇 ] 渗透测试面试题大集合(详解)(十)RCE (远程代码/命令执行漏洞)相关面试题
  • [20190113]四校联考
  • [51nod1610]路径计数
  • [AIGC] 开源流程引擎哪个好,如何选型?
  • [Android] Upload package to device fails #2720