JavaScript中轨迹点数量不一致时如何计算计划轨迹与实际轨迹的平均距离
解决轨迹点数量不一致时的平均距离计算问题
Hey there! 针对你用JavaScript处理飞行轨迹时遇到的「计划轨迹和实际轨迹点数量不一致,没法直接计算平均距离」的问题,我给你整理了两个实用的解决方案,结合你已经用Chart.js做了可视化的场景,这些方法都能快速落地:
方法1:基于时间戳的线性插值(优先推荐)
飞行轨迹几乎都会带时间戳信息对吧?咱可以利用时间维度对齐两条轨迹,通过插值生成对应时间点的坐标,这样就能让两条轨迹的点数量完全匹配了。
步骤拆解:
- 先确保两条轨迹的所有点都按
timestamp字段从小到大排序(计划轨迹和实际轨迹的时间范围最好重叠,不然要处理首尾的缺失部分)。 - 选其中一条轨迹作为「基准轨迹」(比如点更多的那条,或者直接用计划轨迹的时间点)。
- 对基准轨迹的每个时间点,在另一条轨迹里找到相邻的两个时间点,用线性插值算出该时间点对应的经纬度/空间坐标。
- 用Haversine公式(专门算球面两点距离,适合飞行轨迹)计算每对对应点的距离。
- 把所有距离求和后除以点的数量,就是平均距离。
代码示例:
// 1. 实现Haversine公式,计算地球表面两点的球面距离(单位:公里) function haversineDistance(coords1, coords2) { const R = 6371; // 地球半径(公里) const lat1 = coords1.lat * Math.PI / 180; const lon1 = coords1.lon * Math.PI / 180; const lat2 = coords2.lat * Math.PI / 180; const lon2 = coords2.lon * Math.PI / 180; const dLat = lat2 - lat1; const dLon = lon2 - lon1; const a = Math.sin(dLat/2)**2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon/2)**2; const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; } // 2. 根据时间戳插值得到对应坐标 function interpolatePoint(targetTime, trackPoints) { // 找到目标时间前后的两个点 let prevPoint = trackPoints[0]; let nextPoint = trackPoints[trackPoints.length - 1]; for (let i = 0; i < trackPoints.length - 1; i++) { if (trackPoints[i].timestamp <= targetTime && trackPoints[i+1].timestamp >= targetTime) { prevPoint = trackPoints[i]; nextPoint = trackPoints[i+1]; break; } } // 线性插值计算lat和lon const timeRatio = (targetTime - prevPoint.timestamp) / (nextPoint.timestamp - prevPoint.timestamp); const interpolatedLat = prevPoint.lat + (nextPoint.lat - prevPoint.lat) * timeRatio; const interpolatedLon = prevPoint.lon + (nextPoint.lon - prevPoint.lon) * timeRatio; return { lat: interpolatedLat, lon: interpolatedLon }; } // 3. 计算平均距离 function calculateAverageDistance(plannedTrack, actualTrack) { // 先按时间排序(确保输入未排序的情况也能处理) const sortedPlanned = [...plannedTrack].sort((a,b) => a.timestamp - b.timestamp); const sortedActual = [...actualTrack].sort((a,b) => a.timestamp - b.timestamp); let totalDistance = 0; let validPoints = 0; // 以计划轨迹为基准,遍历每个时间点 for (const plannedPoint of sortedPlanned) { // 只处理实际轨迹时间范围内的点 if (plannedPoint.timestamp >= sortedActual[0].timestamp && plannedPoint.timestamp <= sortedActual[sortedActual.length-1].timestamp) { const actualPoint = interpolatePoint(plannedPoint.timestamp, sortedActual); totalDistance += haversineDistance(plannedPoint, actualPoint); validPoints++; } } return validPoints > 0 ? totalDistance / validPoints : 0; }
方法2:等间隔采样(无时间戳时使用)
如果你的轨迹数据没有时间戳,那咱可以按轨迹的累计长度来做等间隔采样,把两条轨迹都转换成相同数量的点,再计算平均距离。
步骤拆解:
- 分别计算两条轨迹的「累计距离数组」:每个元素是从起点到当前点的总距离。
- 确定采样点数N(可以取两条原轨迹点数的最大值,或者自定义一个合适的数值,比如100个点)。
- 对每条轨迹,按等间隔的累计距离值,插值生成N个采样点。
- 计算对应采样点的距离,求平均。
代码示例:
// 1. 计算轨迹的累计距离数组 function getCumulativeDistances(trackPoints) { const distances = [0]; let total = 0; for (let i = 1; i < trackPoints.length; i++) { total += haversineDistance(trackPoints[i-1], trackPoints[i]); distances.push(total); } return distances; } // 2. 根据累计距离插值得到采样点 function sampleTrack(trackPoints, numSamples) { const cumulativeDistances = getCumulativeDistances(trackPoints); const totalDistance = cumulativeDistances[cumulativeDistances.length - 1]; const samples = []; for (let i = 0; i < numSamples; i++) { const targetDistance = (totalDistance * i) / (numSamples - 1); // 找到目标距离对应的区间 let prevIdx = 0; let nextIdx = trackPoints.length - 1; for (let j = 0; j < cumulativeDistances.length - 1; j++) { if (cumulativeDistances[j] <= targetDistance && cumulativeDistances[j+1] >= targetDistance) { prevIdx = j; nextIdx = j+1; break; } } const prevPoint = trackPoints[prevIdx]; const nextPoint = trackPoints[nextIdx]; const distanceRatio = (targetDistance - cumulativeDistances[prevIdx]) / (cumulativeDistances[nextIdx] - cumulativeDistances[prevIdx]); const sampledLat = prevPoint.lat + (nextPoint.lat - prevPoint.lat) * distanceRatio; const sampledLon = prevPoint.lon + (nextPoint.lon - prevPoint.lon) * distanceRatio; samples.push({ lat: sampledLat, lon: sampledLon }); } return samples; } // 3. 计算平均距离 function calculateAverageDistanceWithoutTimestamp(plannedTrack, actualTrack, numSamples = 100) { const sampledPlanned = sampleTrack(plannedTrack, numSamples); const sampledActual = sampleTrack(actualTrack, numSamples); let totalDistance = 0; for (let i = 0; i < numSamples; i++) { totalDistance += haversineDistance(sampledPlanned[i], sampledActual[i]); } return totalDistance / numSamples; }
额外小贴士:
- 飞行轨迹一定要用球面距离公式(比如上面的Haversine),别用平面的欧氏距离,不然误差会很大!
- 如果两条轨迹的时间范围不重叠,记得在代码里过滤掉超出范围的点,避免插值出不合理的坐标。
- 你已经用Chart.js做了可视化,计算完平均距离后,可以把对应点的连线或者距离标注加到图表里,让结果更直观~
内容的提问来源于stack exchange,提问作者Ori




