在MongoDB中,我如何将文档中的字段用作$geoWithin/$centerSphere表达式的输入?

7

我正在尝试编写一个MongoDB查询,以在指定位置为中心的半径内搜索文档。

下面的查询有效。它找到所有在searching.coordinates周围searching.radius弧度内的文档。

但是,我想做的是将当前文档的allowed_radius值添加到searching.radius值中,以便允许的球体实际上更大。

如何构造这个查询以使这种情况成为可能?

现有查询:

collection.aggregate([
        {
            $project:{
                location: "$location",
                allowed_radius: "$allowed_radius"
            }
        },
        {
            $match: {
                $and:
                    [
                        { location: { $geoWithin: { $centerSphere: [ searching.coordinates, searching.radius ] }}},
                        {...},
                    ...]
                ...}
]);

我想要做什么(伪查询):

collection.aggregate([
        {
            $project:{
                location: "$location",
                allowed_radius: "$allowed_radius"
            }
        },
        {
            $match: {
                $and:
                    [
                        { location: { $geoWithin: { $centerSphere: [ searching.coordinates, { $add: [searching.radius, $allowed_radius]} ] }}},
                        {...},
                    ...]
                ...}
]);
1个回答

5

我尝试使用$geoWithin / $centerSphere,但是无法使其工作。

这里还有另一种方法,使用$geoNear操作符:

给定以下输入:

db.collection.insert({
  "airport": "LGW",
  "id": 1,
  "location": { type: "Point", coordinates: [-0.17818, 51.15609] },
  "allowed_radius": 100
})
db.collection.insert({
  "airport": "LGW",
  "id": 2,
  "location": { type: "Point", coordinates: [-0.17818, 51.15609] },
  "allowed_radius": 0
})
db.collection.insert({
  "airport": "ORY",
  "id": 3,
  "location": { type: "Point", coordinates: [2.35944, 48.72528] },
  "allowed_radius": 10
})

这个索引($geoNear所需的):

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

当 searching.radius = 1000 时:

db.collection.aggregate([
  { $geoNear: {
    near: { "type" : "Point", "coordinates":  [7.215872, 43.658411] },
    distanceField: "distance",
    spherical: true,
    distanceMultiplier: 0.001
  }},
  { $addFields: { radius: { "$add": ["$allowed_radius", 1000] } } },
  { $addFields: { isIn: { "$subtract": ["$distance", "$radius" ] } } },
  { $match: { isIn: { "$lte": 0 } } }
])

该查询将返回id为1(distance=1002 <= radius=1000+100)和3(distance=676 <= radius=1000+10)的文档,并且舍弃id为2(distance=1002 > 1000+0)的文档。

distanceMultiplier参数用于将单位转换为千米。

$geoNear必须是聚合管道中的第一个阶段(我认为是由于使用了索引),但$geoNear的一个参数是其他字段的匹配查询。

即使需要地理空间索引,也可以将附加维度添加到索引中。

$geoNear不将位置字段作为参数,因为它要求集合具有地理空间索引。因此,$geoNear隐式地使用被索引的字段作为位置字段(无论该字段的名称是什么)。

最后,我相信最后几个阶段可以简化。

$geoNear阶段仅用于在每条记录上投影距离:

{ "airport" : "ORY", "distance" : 676.5790971238937, "location" : { "type" : "Point", "coordinates" : [ 2.35944, 48.72528 ] }, "allowed_radius" : 10, "id" : 3 }
{ "airport" : "LGW", "distance" : 1002.3351814526812, "location" : { "type" : "Point", "coordinates" : [ -0.17818, 51.15609 ] }, "allowed_radius" : 100, "id" : 1 }
{ "airport" : "LGW", "distance" : 1002.3351814526812, "location" : { "type" : "Point", "coordinates" : [ -0.17818, 51.15609 ] }, "allowed_radius" : 0, "id" : 2 }

实际上,geoNear操作符需要使用distanceField参数,该参数用于将计算出的距离在查询后续阶段中投影到每个记录上。在聚合结束时,返回的记录如下:

{
  "airport" : "ORY",
  "location" : { "type" : "Point", "coordinates" : [ 2.35944, 48.72528 ] },
  "allowed_radius" : 10,
  "id" : 3,
  "distance" : 676.5790971238937,
  "radius" : 1010,
  "isIn" : -333.4209028761063
}

如有需要,您可以使用最终的$project阶段来删除查询产生的字段(如distance、radius、isIn)。例如:{"$project":{"distance":0}}

谢谢!有趣的想法!距离字段是否持久化,还是仅适用于每个查询? - CodyBugstein
在查询中,哪一部分表明“使用文档的location字段进行比较”? - CodyBugstein
不客气!“距离字段”被投影并在聚合的下一阶段中提供,并在输出记录中返回(当管道中不再需要时,可以使用$project阶段删除)。但是集合中的记录不会使用此字段进行更新(不确定是否回答了问题)。“位置字段”(无论其名称如何)不是$geoNear运算符的参数,因为它是从强制性地理空间索引中隐含推断出来的。我已经编辑了答案,加入了更多细节。 - Xavier Guihot
谢谢 @Xavier。我一直在遇到一些问题,我意识到问题的一部分是 geoNear 只允许一个 2dsphere 索引,而我的文档实际上有两个位置。所以这个答案很好,但在我的情况下它并不能完全解决问题。 - CodyBugstein

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接