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

GeoFire查询结果未按距离排序问题求助

解决GeoFire返回地标列表未按距离排序的问题

首先说明当前使用的依赖版本:

geofire#4.1.2 bower_components\geofire └── firebase#3.9.0

我之前写了一个根据用户位置返回地标列表的函数,原本能正常按距离排序,但最近做了以下操作后排序功能失效了:

  • 添加Swagger
  • 调整项目结构以符合OpenAPI规范
  • 添加bower
  • 重新安装geofire和firebase

我的函数代码如下:

GetAllLandmarksByUserLocation(ref, ref_locations, properties, user_latitude, user_longitude, user_radius) { const landmarkGeoFire = new GeoFire(ref_locations); var geoQuery = landmarkGeoFire.query({ center: [user_latitude, user_longitude], radius: user_radius }); return new Promise(function(resolve,reject) { var locations = []; var onKeyEnteredRegistration = geoQuery.on("key_entered", function (key, coordinates, distance) { var location = {}; location.key = key; location.latitude = coordinates[0]; location.longitude = coordinates[1]; location.distance = distance; locations.push(location); }); var attributes = []; var onReadyRegistration = geoQuery.on("ready", function() { ref.on('value', function (refsSnap) { refsSnap.forEach((refSnap) => { var list = refSnap; locations.forEach(function(locationSnap) { //console.log(refSnap.key, '==', locationSnap.key); // brute force approach, rework this later if (refSnap.key == locationSnap.key) { var attribute = {}; for(var i=0; i<=properties.length-1; i++) { if(properties[i] == 'key') { attribute[properties[i]] = refSnap.key; continue; } attribute[properties[i]] = list.child(properties[i]).val(); } attribute['latitude'] = locationSnap.latitude; attribute['longitude'] = locationSnap.longitude; attribute['distance'] = locationSnap.distance; attributes.push(attribute); } // refSnap.key == locationSnap.key }); // locations.forEach }); // refsSnap.forEach resolve(attributes); }); // ref.on }); // onreadyregistration }); // Promise }

Firebase实时数据库的结构如下:
Firebase RTDB结构

测试用的输入参数:

  • user_latitude = 40.769841
  • user_longitude = -73.964306
  • user_radius = 225

当前返回的结果未按距离排序,示例如下:

[ { "key": "-L8AURBnFy01EZVekPnR", "building": "753", "street": "York Avenue", "category": 0, "closing": "6 PM", "email": "cornell@somewhere.com", "name": "Weill Cornell", "opening": "8 AM", "phone": "212-555-1212", "postal": 10021, "timestamp": "Wed Mar 21 2018 22:15:55 GMT-0400 (Eastern Daylight Time)", "type": 4, "web": "http://www.weillcornell.com", "latitude": 40.765982, "longitude": -73.956045, "distance": 0.817387865219758 }, { "key": "-L8AUdrIysiTYr0RUDV_", "building": "753", "street": null, "category": 0, "closing": "6 PM", "email": "cornell@somewhere.com", "name": "Weill Cornell", "opening": "8 AM", "phone": "212-555-1212", "postal": 10021, "timestamp": "Wed Mar 21 2018 22:16:51 GMT-0400 (Eastern Daylight Time)", "type": 4, "web": "http://www.weillcornell.com", "latitude": 40.765982, "longitude": -73.956045, "distance": 0.817387865219758 }, { "key": "-L8AUeEayBJKU3y6Hd4e", "building": "753", "street": null, "category": 0, "closing": "6 PM", "email": "cornell@somewhere.com", "name": "Weill Cornell", "opening": "8 AM", "phone": "212-555-1212", "postal": 10021, "timestamp": "Wed Mar 21 2018 22:16:53 GMT-0400 (Eastern Daylight Time)", "type": 4, "web": "http://www.weillcornell.com", "latitude": 40.765982, "longitude": -73.956045, "distance": 0.817387865219758 }, { "key": "-L8AVW50fx3cTwn-DrIQ", "building": "753", "street": "Minetta Lane", "category": 0, "closing": "6 PM", "email": "support@southebys.com", "name": "Southebys", "opening": "8 AM", "phone": "212-555-1212", "postal": 10021, "timestamp": "Wed Mar 21 2018 22:20:38 GMT-0400 (Eastern Daylight Time)", "type": 4, "web": "http://www.southebys.com", "latitude": 40.766372, "longitude": -73.953803, "distance": 0.9649556738877696 }, { "key": "-L8AV_nVSAzMxdAXJb-L", "building": "753", "street": null, "category": 0, "closing": "6 PM", "email": "support@southebys.com", "name": "Southebys", "opening": "8 AM", "phone": "212-555-1212", "postal": 10021, "timestamp": "Wed Mar 21 2018 22:20:57 GMT-0400 (Eastern Daylight Time)", "type": 4, "web": "http://www.southebys.com", "latitude": 40.766372, "longitude": -73.953803, "distance": 0.9649556738877696 }, { "key": "-L8AWqfH3q4dZcrFMc6F", "building": "753", "street": null, "category": 0, "closing": "6 PM", "email": "support@helmsley.com", "name": "helmsley", "opening": "8 AM", "phone": "212-555-1212", "postal": 10021, "timestamp": "Wed Mar 21 2018 22:26:28 GMT-0400 (Eastern Daylight Time)", "type": 4, "web": "http://www.helmsl..."} ]

问题根源

其实GeoFire的key_entered事件触发顺序从来都不保证是按距离排序的,之前能正常排序可能只是巧合或者旧版本的偶然行为。加上后续匹配Firebase数据和地理坐标时,是按遍历顺序直接添加到结果数组,没有做任何排序处理,所以现在出现了排序混乱的情况。

修复方案

我们需要在最终返回结果前,手动对attributes数组按照distance字段做升序排序,同时还可以优化数据匹配逻辑提升性能:

1. 添加排序逻辑

在resolve(attributes);这行代码之前,插入排序代码:

attributes.sort((a, b) => a.distance - b.distance);

2. 优化数据匹配逻辑

之前用双重循环做匹配效率较低,我们可以把locations数组转成以key为键的对象,匹配时直接通过key查找,时间复杂度从O(n²)降到O(n):

// 在onReadyRegistration回调里,先把locations转成映射对象
const locationMap = {};
locations.forEach(location => {
  locationMap[location.key] = location;
});

// 遍历Firebase数据时直接通过key匹配
refsSnap.forEach((refSnap) => {
  const location = locationMap[refSnap.key];
  if (location) {
    // 保留原本构建attribute的逻辑
    var attribute = {};
    for(var i=0; i<=properties.length-1; i++) {
      if(properties[i] == 'key') {
        attribute[properties[i]] = refSnap.key;
        continue;
      }
      attribute[properties[i]] = refSnap.child(properties[i]).val();
    }
    attribute['latitude'] = location.latitude;
    attribute['longitude'] = location.longitude;
    attribute['distance'] = location.distance;
    attributes.push(attribute);
  }
});

3. 完整修改后的函数

整合以上修改后的完整函数代码:

GetAllLandmarksByUserLocation(ref, ref_locations, properties, user_latitude, user_longitude, user_radius) { 
  const landmarkGeoFire = new GeoFire(ref_locations); 
  var geoQuery = landmarkGeoFire.query({ 
    center: [user_latitude, user_longitude], 
    radius: user_radius 
  }); 
  return new Promise(function(resolve,reject) { 
    var locations = []; 
    var onKeyEnteredRegistration = geoQuery.on("key_entered", function (key, coordinates, distance) { 
      var location = {}; 
      location.key = key; 
      location.latitude = coordinates[0]; 
      location.longitude = coordinates[1]; 
      location.distance = distance; 
      locations.push(location); 
    }); 

    var attributes = []; 
    var onReadyRegistration = geoQuery.on("ready", function() { 
      // 优化:将locations转为key映射的对象,提升匹配效率
      const locationMap = {};
      locations.forEach(location => {
        locationMap[location.key] = location;
      });

      ref.on('value', function (refsSnap) { 
        refsSnap.forEach((refSnap) => { 
          const location = locationMap[refSnap.key];
          if (location) {
            var attribute = {}; 
            for(var i=0; i<=properties.length-1; i++) { 
              if(properties[i] == 'key') { 
                attribute[properties[i]] = refSnap.key; 
                continue; 
              } 
              attribute[properties[i]] = refSnap.child(properties[i]).val(); 
            } 
            attribute['latitude'] = location.latitude; 
            attribute['longitude'] = location.longitude; 
            attribute['distance'] = location.distance; 
            attributes.push(attribute); 
          }
        }); 
        // 对结果按距离升序排序
        attributes.sort((a, b) => a.distance - b.distance);
        resolve(attributes); 
        // 取消监听,避免内存泄漏
        onKeyEnteredRegistration.cancel();
      }); 
    }); 
  }); 
}

额外提醒

  • 记得在ready事件处理完成后取消key_entered的监听,避免内存泄漏(上面的代码已经添加了onKeyEnteredRegistration.cancel();)
  • 如果地标数据量很大,建议考虑在Firebase端做数据过滤,减少客户端需要处理的数据量,提升整体性能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:08:14