You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动