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

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
  }
}
问题原因分析
  1. 位置精度延迟:Geolocation.watchPosition首次返回的可能是缓存的低精度位置,后续才会获取到高精度真实位置。首次查询基于低精度位置计算的geohash范围更小,导致漏查附近用户。
  2. Geohash范围查询缺陷:直接用经纬度极值的geohash作为查询范围,无法完全覆盖目标区域(geohash字典序范围与经纬度矩形不完全匹配),部分符合条件的用户geohash不在查询范围内。
  3. 硬编码用户ID错误:findNearbyUsers中硬编码的currentUserID = '123'与组件中的userId = '1234'不一致,可能导致错误过滤或保留自身数据,影响结果准确性。
  4. 数据库索引缺失:若未在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 05:45:19