You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firestore查询文档后计算距离出现NaN的问题排查

经纬度计算距离返回NaN问题排查与解决

我开发的一款基于经纬度查找最近位置的应用,在通过Firestore查询获取位置文档后,调用Haversine公式计算距离时返回了NaN值。最初怀疑是Firestore查询未完成就执行后续代码,但Firebase文档明确说明getDocs会等待查询完成后返回,且不会返回undefined。检查了位置文档,确认lat和long字段都存在。


核心逻辑代码

export default async function FindAllLocOfActAndCalcShortestDist(
  activity,
  eqPtLat,
  eqPtLong,
  setCalculatedLocDoc,
  setCalculatedLocDocFound
) {
  const locRef = collection(db, "locations");

  const q = query(locRef, where("activity", "==", activity));

  let shortestDist = 10000000000;
  let closestLocDoc;

  const querySnapshot = await getDocs(q);

  querySnapshot.forEach((locationDoc) => {
    let locDocData = locationDoc.data();
    let distance = HaversineFormula(
      eqPtLat,
      eqPtLong,
      locDocData.lat,
      locDocData.long
    );
    if (distance < shortestDist) {
      closestLocDoc = locationDoc;
      shortestDist = distance;
    }
  })

  setCalculatedLocDoc(closestLocDoc);
  setCalculatedLocDocFound(true);
}

export default function HaversineFormula(lat1, long1, lat2, long2) {
  const toRadian = angle => (Math.PI / 180) * angle;
  const distance = (a, b) => (Math.PI / 180) * (a - b);
  const RADIUS_OF_EARTH_IN_KM = 6371;

  const dLat = distance(lat2, lat1);
  const dLon = distance(long2, long1);

  lat1 = toRadian(lat1);
  lat2 = toRadian(lat2);

  const a =
    Math.pow(Math.sin(dLat / 2), 2) +
    Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
  const c = 2 * Math.asin(Math.sqrt(a));

  let finalDistance = RADIUS_OF_EARTH_IN_KM * c;

  console.log(finalDistance);
  return finalDistance;
}

调用与辅助函数

// 触发查找位置的方法
const findLocations = () => {
  setSearchedLocations(true);
  CalcEqPtAndGetArrOfFriendsPref(
    profile,
    chosen_friends,
    friendList,
    setEqPtLat,
    setEqPtLong
  )
    .then((arrOfPref) => DetermineActType(arrOfPref))
    .then((activity) => FindAllLocOfActAndCalcShortestDist(activity, eqPtLat, eqPtLong, setCalculatedLocDoc, setCalculatedLocDocFound))
    .catch((e) => {console.log(e)}); 
  handleOpenOutingPopup();
};

export default async function CalcEqPtAndGetArrOfFriendsPref(profile, chosen_friends, friendList, setEqPtLat, setEqPtLong) {
  let prefArr = [profile.pref];
  let coordsArr = [[profile.lat, profile.long]]
  const arrOfFriendIDsWithZero = chosen_friends.map((friendBool, ind) => {
    if (friendBool) {
      return friendList[ind].friend_id;
    } else {
      return 0;
    }
  });

  const arrOfFriendIDs = arrOfFriendIDsWithZero.filter(n=>n!==0);

  const usersRef = collection(db, "users");
  const q = query(usersRef, where("user_id", "in", arrOfFriendIDs));
  const querySnapshot = await getDocs(q);

  querySnapshot.forEach((userDoc) => {
    const profData = userDoc.data();
    const friendPref = profData.pref;
    const friendCoords = [profData.lat, profData.long];
    prefArr.push(friendPref);
    coordsArr.push(friendCoords);
  })

  DetermineEqPt(coordsArr, setEqPtLat, setEqPtLong);
  return prefArr;
}

export default async function DetermineActType(arrOfArrOfBool) {
    let arrOfCount = new Array(arrOfArrOfBool[0]).fill(0);
    arrOfArrOfBool.map((arrOfBool, ind1) => {
        arrOfBool.map((bool, ind2) => {
            if (bool) {
                arrOfCount[ind2] += 1
            }
        })
    })
    let max_value = 0;
    let max_index = -1;
    for (let i = 0; i < arrOfCount.length; i++) {
        if (arrOfCount[i] > max_value) {
            max_value = arrOfCount[i];
            max_index = i
        }
    }
    return max_index;
}

export default function DetermineEqPt(arrOfCoords, setEqPtLat, setEqPtLong) {
    let totalLat = 0;
    let totalLong = 0;

    for (let i = 0; i < arrOfCoords.length; i++) {
        totalLat += arrOfCoords[i][0];
        totalLong += arrOfCoords[i][1];
    }

    let centroid_lat = totalLat / arrOfCoords.length;
    let centroid_long = totalLong / arrOfCoords.length

    setEqPtLat(centroid_lat);
    setEqPtLong(centroid_long);
}

排查方向与解决方法

  1. 参数有效性校验
    NaN通常出现在非数字参与数学运算时,给Haversine公式添加参数校验,提前拦截无效值:
export default function HaversineFormula(lat1, long1, lat2, long2) {
  // 校验参数是否为有效数字
  if (isNaN(lat1) || isNaN(long1) || isNaN(lat2) || isNaN(long2)) {
    console.error('无效经纬度参数:', lat1, long1, lat2, long2);
    return Infinity; // 返回极大值,不影响最短距离筛选
  }
  // 原有公式逻辑...
}
  1. 解决React状态异步更新问题
    findLocations中调用CalcEqPtAndGetArrOfFriendsPref后,直接使用eqPtLat/eqPtLong状态值会拿到旧值(因为状态更新是异步的)。修改CalcEqPtAndGetArrOfFriendsPref直接返回计算好的中心点坐标:
export default async function CalcEqPtAndGetArrOfFriendsPref(profile, chosen_friends, friendList) {
  let prefArr = [profile.pref];
  let coordsArr = [[profile.lat, profile.long]];
  const arrOfFriendIDsWithZero = chosen_friends.map((friendBool, ind) => {
    if (friendBool) return friendList[ind].friend_id;
    return 0;
  });
  const arrOfFriendIDs = arrOfFriendIDsWithZero.filter(n => n !== 0);

  const usersRef = collection(db, "users");
  const q = query(usersRef, where("user_id", "in", arrOfFriendIDs));
  const querySnapshot = await getDocs(q);

  querySnapshot.forEach((userDoc) => {
    const profData = userDoc.data();
    prefArr.push(profData.pref);
    coordsArr.push([profData.lat, profData.long]);
  });

  // 直接计算中心点
  let totalLat = 0, totalLong = 0;
  coordsArr.forEach(coord => {
    totalLat += coord[0];
    totalLong += coord[1];
  });
  const centroid_lat = totalLat / coordsArr.length;
  const centroid_long = totalLong / coordsArr.length;

  return { prefArr, centroid_lat, centroid_long };
}

同步修改调用逻辑:

const findLocations = () => {
  setSearchedLocations(true);
  CalcEqPtAndGetArrOfFriendsPref(profile, chosen_friends, friendList)
    .then(({ prefArr, centroid_lat, centroid_long }) => {
      setEqPtLat(centroid_lat);
      setEqPtLong(centroid_long);
      return DetermineActType(prefArr);
    })
    .then((activity) => FindAllLocOfActAndCalcShortestDist(activity, centroid_lat, centroid_long, setCalculatedLocDoc, setCalculatedLocDocFound))
    .catch(e => console.log(e)); 
  handleOpenOutingPopup();
};
  1. 校验Firestore数据类型
    确认locations集合中的lat和long是数字类型而非字符串,可在遍历文档时添加日志排查:
querySnapshot.forEach((locationDoc) => {
  let locDocData = locationDoc.data();
  console.log(`${locationDoc.id} - lat类型: ${typeof locDocData.lat}, 值: ${locDocData.lat}`);
  console.log(`${locationDoc.id} - long类型: ${typeof locDocData.long}, 值: ${locDocData.long}`);
  // 原有计算逻辑...
})

内容的提问来源于stack exchange,提问作者Sean

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 19:39:54