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); }
排查方向与解决方法
- 参数有效性校验
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; // 返回极大值,不影响最短距离筛选 } // 原有公式逻辑... }
- 解决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(); };
- 校验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
相关产品推荐
相关产品推荐

