Mongoose Geo Near Search - 如何在给定距离内排序?

Posted

技术标签:

【中文标题】Mongoose Geo Near Search - 如何在给定距离内排序?【英文标题】:Mongoose Geo Near Search - How to sort within a given distance? 【发布时间】:2020-08-04 04:28:14 【问题描述】:

我正在使用猫鼬和带有 maxDistance 的近查询来过滤靠近给定 gps 位置的元素。但是,近查询会覆盖其他排序。我想要的是找到给定点的 maxDistance 内的所有元素,然后按其他属性排序。 这是我目前正在做的一个例子:

架构:

mongoose.Schema(
    name: 
        type: String,
        required: true
    ,
    score: 
        type: Number,
        required: true,
        default: 0
    ,
    location: 
        type: 
            type: String,
            default: 'Point',
        ,
        coordinates: 
            type: [Number]
        
    ,
    ....
);

查询:

model.find(
  "location.coordinates": 
    "$near": 
      "$maxDistance": 1000,
      "$geometry": 
        "type": "Point",
        "coordinates": [
          10,
          10
        ]
      
    
  
).sort('-score');

在 find 之后添加 .sort 在这里没有帮助,并且无论如何都会按近的顺序返回项目。

【问题讨论】:

score 是模型的一个属性。我现在包含了架构。我要做的是找到给定gps坐标1000米内的所有项目,然后按分数排序。之后我当然可以在查询之外进行排序,但是为了可扩展性,我想找到一个查询解决方案来排序...... sort('-score') 是否适用于反向排序?我一直认为sort(score:-1) 是的,如果没有近乎查询,它适用于按降序排序。我想它可能在版本上有所发展(参考:***.com/a/15081087/2099833),并且不再流行,但它对我来说效果很好,因为我在 get 参数中采用 sort 属性以允许客户端随意排序,所以它比使用对象符号更容易.. 好的,你想先用距离排序,然后用score吗? 您能出示您的示例文件吗?并从查询中删除coordinates,它只能是location: $near: ... 【参考方案1】:

在查找查询中,您需要使用location 而不是location.coordinates

router.get("/test", async (req, res) => 
  const lat = 59.9165591;
  const lng = 10.7881978;
  const maxDistanceInMeters = 1000;

  const result = await model
    .find(
      location: 
        $near: 
          $geometry: 
            type: "Point",
            coordinates: [lng, lat],
          ,
          $maxDistance: maxDistanceInMeters,
        ,
      ,
    )
    .sort("-score");

  res.send(result);
);

要使 $near 工作,您需要相关集合上的 2dsphere 索引:

db.collection.createIndex(  "location" : "2dsphere"  )

在 mongodb $near 文档中它说:

$near 按距离对文档进行排序。如果您还包括一个 sort() 查询,sort() 有效地重新排序匹配的文档 覆盖 $near 已经执行的排序操作。使用时 sort() 与地理空间查询,考虑使用 $geoWithin 运算符, 它不对文档进行排序,而不是 $near。

由于您对按距离排序不感兴趣,正如 Nic 所指出的,使用 $near 是不必要的,最好像这样使用$geoWithin:

router.get("/test", async (req, res) => 
  const lat = 59.9165591;
  const lng = 10.7881978;
  const distanceInKilometer = 1;
  const radius = distanceInKilometer / 6378.1;

  const result = await model
    .find(
      location:  $geoWithin:  $centerSphere: [[lng, lat], radius]  ,
    )
    .sort("-score");

  res.send(result);
);

为了计算半径,我们将公里除以 6378.1,将英里除以 3963.2,如 here 所述。

所以这将找到1公里半径内的位置。

示例文档:

[
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.7741692,
                59.9262198
            ]
        ,
        "score": 50,
        "_id": "5ea9d4391e468428c8e8f505",
        "name": "Name1"
    ,
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.7736078,
                59.9246991
            ]
        ,
        "score": 70,
        "_id": "5ea9d45c1e468428c8e8f506",
        "name": "Name2"
    ,
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.7635027,
                59.9297932
            ]
        ,
        "score": 30,
        "_id": "5ea9d47b1e468428c8e8f507",
        "name": "Name3"
    ,
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.7635027,
                59.9297932
            ]
        ,
        "score": 40,
        "_id": "5ea9d4971e468428c8e8f508",
        "name": "Name4"
    ,
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.7768093,
                59.9287668
            ]
        ,
        "score": 90,
        "_id": "5ea9d4bd1e468428c8e8f509",
        "name": "Name5"
    ,
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.795769,
                59.9190384
            ]
        ,
        "score": 60,
        "_id": "5ea9d4e71e468428c8e8f50a",
        "name": "Name6"
    ,
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.1715157,
                59.741873
            ]
        ,
        "score": 110,
        "_id": "5ea9d7d216bdf8336094aa92",
        "name": "Name7"
    
]

输出:(1km以内,按分数降序排列)

[
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.7768093,
                59.9287668
            ]
        ,
        "score": 90,
        "_id": "5ea9d4bd1e468428c8e8f509",
        "name": "Name5"
    ,
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.7736078,
                59.9246991
            ]
        ,
        "score": 70,
        "_id": "5ea9d45c1e468428c8e8f506",
        "name": "Name2"
    ,
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.795769,
                59.9190384
            ]
        ,
        "score": 60,
        "_id": "5ea9d4e71e468428c8e8f50a",
        "name": "Name6"
    ,
    
        "location": 
            "type": "Point",
            "coordinates": [
                10.7741692,
                59.9262198
            ]
        ,
        "score": 50,
        "_id": "5ea9d4391e468428c8e8f505",
        "name": "Name1"
    
]

【讨论】:

非常感谢。效果很好:) 快速提问,你知道设置使用单位的方法吗?我可以表明我在某处使用公里而不是英里吗? @DanielValland 抱歉,我没有注意到有关单位的问题。我在 mongo 文档中找不到任何有用的东西,但是您可以创建一个实用程序帮助函数来进行转换。【参考方案2】:

$near 按距离对文档进行排序,这在这里很浪费。使用不对文档进行排序的$geoWithin 可能会更好。比如:

model.find(
  "location.coordinates":  
     $geoWithin:  $center: [ [-74, 40.74], <radius> ]   
).sort(score: -1);

$center 文档有更多详细信息。

【讨论】:

以上是关于Mongoose Geo Near Search - 如何在给定距离内排序?的主要内容,如果未能解决你的问题,请参考以下文章

在 $or 中运行多个 geo $near 查询

Mongoose 无法对位置数组执行 $near 查询

Mongoose - FindOne 和 Search inside 和 Array

使用 geonear 的 Mongoose 聚合

Mongoose 找到第一个最近的位置

求助:这个需求 Elastic Search 能实现吗?