数字孪生项目中实时数据驱动多节相连车厢沿轨道运行
数字孪生项目中实时数据驱动多节相连车厢沿轨道运行
用户希望在数字孪生应用中真实的还原运输列车在轨道上的位置信息,模型制作动画,数据驱动动画的方式显然是不行了,动画毕竟是一个整体的,很难通过数据精准控制。
第一步:拆分列车
整个列车需要拆分成单节的,如果不拆分的话,拐弯的位置就会斜出轨道,大概就是下图的样子。
所以需要每一节车厢都是一个独立的对象,分别作控制。
第二步:数据问题
大部分客户能给到的数据还是通过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,在这一章我们不做赘述,感兴趣的小伙伴可以留言,人多的话,单独出一篇文章介绍。