React Native中Firebase实时数据库地理查询数据不一致问题
问题描述
在React Native应用中实现用户位置更新与附近用户查询功能时,遇到首次地理查询返回结果比预期少,后续查询才能返回正确结果的问题。尝试过geofire-js但与@react-native-firebase/database不兼容,且因读写量较大无法使用Firestore,需解决该查询不一致问题。
相关代码
位置监听与更新组件代码
const [location, setLocation] = useState<any>() const userId = '1234' const updateUserLocation = async (position: any) => { const { latitude, longitude } = position.coords await database() .ref(`usersLocations/${userId}`) .set(encodeLocationObject({ latitude, longitude })) console.log('location updated') } useEffect(() => { let watchId: number const handleLocationChange = async (position: any) => { setLocation(position) await updateUserLocation(position) setTimeout(async () => { const nearbyUsers = await findNearbyUsers(position) console.log(nearbyUsers) }, 5000) } const startWatchingLocation = async () => { watchId = Geolocation.watchPosition( handleLocationChange, error => { Alert.alert(`Error getting location: ${error.message}`) }, { enableHighAccuracy: true, distanceFilter: 10, }, ) } startWatchingLocation() return () => { Geolocation.clearWatch(watchId) } }, [])
附近用户查询函数
export const findNearbyUsers = async (userLocation: GeoPosition) => { try { const {latitude, longitude} = userLocation.coords const geohash = ngeohash.encode(latitude, longitude) const RADIUS = USER_DISCOVER_RANGE + 50 const center = ngeohash.decode(geohash) const latPerMeter = 1 / 111111 const lonPerMeter = Math.abs(Math.cos(center.latitude) * latPerMeter) const latMin = center.latitude - latPerMeter * RADIUS const latMax = center.latitude + latPerMeter * RADIUS const lonMin = center.longitude - lonPerMeter * RADIUS const lonMax = center.longitude + lonPerMeter * RADIUS const dbRef = database() .ref('usersLocations') .orderByChild('g') .startAt(ngeohash.encode(latMin, lonMin)) .endAt(ngeohash.encode(latMax, lonMax)) const users = (await dbRef.once('value')).val() const currentUserID = '123' let nearbyUsers: Array<suggestedUser> = [] console.log(Object.keys(users)); for (let userID of Object.keys(users)) { if (userID !== currentUserID) { const otherLocation = users[userID].l const distance = getDistance({latitude, longitude}, otherLocation) console.log(userID, distance); if (distance < 100) { nearbyUsers.push({distance, id: userID}) } } } dbRef.off('value') return nearbyUsers } catch (error) { console.error(error) throw error } }
问题原因分析
- 位置精度延迟:
Geolocation.watchPosition首次返回的可能是缓存的低精度位置,后续才会获取到高精度真实位置。首次查询基于低精度位置计算的geohash范围更小,导致漏查附近用户。 - Geohash范围查询缺陷:直接用经纬度极值的geohash作为查询范围,无法完全覆盖目标区域(geohash字典序范围与经纬度矩形不完全匹配),部分符合条件的用户geohash不在查询范围内。
- 硬编码用户ID错误:
findNearbyUsers中硬编码的currentUserID = '123'与组件中的userId = '1234'不一致,可能导致错误过滤或保留自身数据,影响结果准确性。 - 数据库索引缺失:若未在Firebase控制台为
usersLocations节点的g字段创建索引,orderByChild('g')的查询可能无法高效、完整返回匹配数据,首次查询时问题尤为明显。
解决方案
1. 确保查询基于高精度位置
在handleLocationChange中添加精度判断,仅当位置精度符合要求时才执行更新和查询,同时替换固定延迟为数据库写入确认:
const handleLocationChange = async (position: any) => { // 仅接受精度在50米以内的位置数据 if (position.coords.accuracy > 50) return setLocation(position) await updateUserLocation(position) // 等待数据库写入确认后再查询 const dbRef = database().ref(`usersLocations/${userId}`) dbRef.once('value', async () => { const nearbyUsers = await findNearbyUsers(position, userId) console.log(nearbyUsers) }) }
2. 优化Geohash范围查询逻辑
改用获取目标区域周边所有geohash格子的方式,确保覆盖所有可能的用户:
// 新增工具函数:获取指定半径内的所有geohash格子 const getSurroundingGeohashes = (lat: number, lon: number, radius: number) => { const precision = 6 // 根据半径调整精度,6级geohash约对应1.2公里范围 const centerHash = ngeohash.encode(lat, lon, precision) const neighbors = ngeohash.neighbors(centerHash) return [centerHash, ...neighbors] } // 修改findNearbyUsers中的查询逻辑 export const findNearbyUsers = async (userLocation: GeoPosition, currentUserId: string) => { try { const {latitude, longitude} = userLocation.coords const RADIUS = USER_DISCOVER_RANGE + 50 const surroundingHashes = getSurroundingGeohashes(latitude, longitude, RADIUS) let nearbyUsers: Array<suggestedUser> = [] // 批量查询每个geohash对应的用户 for (const hash of surroundingHashes) { const dbRef = database() .ref('usersLocations') .orderByChild('g') .startAt(hash) .endAt(hash + '\uf8ff') // 匹配以该hash开头的所有geohash const users = (await dbRef.once('value')).val() if (!users) continue for (let userID of Object.keys(users)) { if (userID === currentUserId) continue const otherLocation = users[userID].l const distance = getDistance({latitude, longitude}, otherLocation) if (distance < 100) { // 去重:避免同一用户被多个geohash格子匹配 if (!nearbyUsers.find(u => u.id === userID)) { nearbyUsers.push({distance, id: userID}) } } } } return nearbyUsers } catch (error) { console.error(error) throw error } }
3. 修复用户ID不一致问题
调用findNearbyUsers时传入当前用户ID,替换硬编码值:
// 在handleLocationChange中调用 const nearbyUsers = await findNearbyUsers(position, userId)
4. 创建Firebase数据库索引
登录Firebase控制台,进入实时数据库 -> 规则,添加以下索引规则:
{ "rules": { "usersLocations": { ".indexOn": "g" } } }
内容的提问来源于stack exchange,提问作者Adnit Kamberi
相关产品推荐
相关产品推荐

