如何从 mongodb 通过 Geowithin 获取匹配的子文档?

Posted

技术标签:

【中文标题】如何从 mongodb 通过 Geowithin 获取匹配的子文档?【英文标题】:How to get matched sub documents by Geowithin from mongodb? 【发布时间】:2018-10-08 05:40:01 【问题描述】:

我在这里要做的是,我只想要那些在提供的纬度和经度内的子文档,但如果我的文档中只有一个子文档匹配而其他子文档不匹配,它应该只返回包含该特定文档的文档。但它也将所有子文档都归还给我,你们可以帮助我。 我的文档是这样的


         "_id": "5ae04fd45f104a5980cf7e0e",
          "name": "Rehan",
         "email": "rehan@gmail.com",
         "status": true,
         "created_at": "2018-04-25T09:52:20.266Z",
         "parking_space": [
             
                 "_id": "5ae05dce5f104a5980cf7e0f",
                 "parking_name": "my space 1",
                 "restriction": "no",
                 "hourly_rate": "3",
                 "location": 
                    "type": "Point",
                    "coordinates": [
                        86.84470799999997,
                        42.7052881
                    ]
                ,
            ,
            
                "_id": "5ae06d4d5f104a5980cf7e52",
                "parking_name": "my space 2",
                "restriction": "no",
                "hourly_rate": "6",
                "location": 
                    "type": "Point",
                    "coordinates": [
                        76.7786787,
                        30.7352527
                    ]
                ,
            
        ],
    ,
    
        "_id": "5ae2f8148d51db4937b9df02",
        "name": "nellima",
        "email": "neel@gmail.com",
        "status": true,
        "created_at": "2018-04-27T10:14:44.598Z",
        "parking_space": [
            

                "_id": "5ae2f89d8d51db4937b9df04",
               "parking_name": "my space 3",
                "restriction": "no",
                "hourly_rate": "60",
              "location": 
                    "type": "Point",
                    "coordinates": [
                        76.7786787,
                        30.7352527
                    ]
                ,
            
        ],
    ,
   

我正在应用这个查询。

User.find(
        "parking_space.location": 
            "$geoWithin": 
                "$centerSphere": [
                    [76.7786787, 30.7352527], 7 / 3963.2
                ]
            
        ,
    , function(err, park_places) 
        if (err) 
            return res.send(
                data: err,
                status: false
            );
         else 
            return res.send(
                data: park_places,
                status: true,
                msg: "Parking data according to location"
            );
        
    );

我正在尝试获取这样的数据。


         "_id": "5ae04fd45f104a5980cf7e0e",
          "name": "Rehan",
         "email": "rehan@gmail.com",
         "status": true,
         "created_at": "2018-04-25T09:52:20.266Z",
         "parking_space": [
            
                "_id": "5ae06d4d5f104a5980cf7e52",
                "parking_name": "my space 2",
                "restriction": "no",
                "hourly_rate": "6",
                "location": 
                    "type": "Point",
                    "coordinates": [
                        76.7786787,
                        30.7352527
                    ]
                ,
            
        ],
    ,
    
        "_id": "5ae2f8148d51db4937b9df02",
        "name": "nellima",
        "email": "neel@gmail.com",
        "status": true,
        "created_at": "2018-04-27T10:14:44.598Z",
        "parking_space": [
            

                "_id": "5ae2f89d8d51db4937b9df04",
               "parking_name": "my space 3",
                "restriction": "no",
                "hourly_rate": "60",
              "location": 
                    "type": "Point",
                    "coordinates": [
                        76.7786787,
                        30.7352527
                    ]
                ,
            
        ],
    ,
   

有没有可能得到这样的数据。

【问题讨论】:

谁能帮帮我 也许这会有所帮助:***.com/questions/36229123/… 您认为提供的答案中是否有某些内容无法解决您的问题?如果是这样,那么请对答案发表评论,以澄清究竟需要解决哪些尚未解决的问题。如果它确实回答了您提出的问题,请注意Accept your Answers您提出的问题 【参考方案1】:

就您在此处尝试执行的操作而言,更好的选择是实际使用$geoNear 聚合管道阶段来确定您的约束范围内的“最近”匹配。值得注意的是,您的标准实际上要求 $geoWithin 通过应用所应用的数学来获得 7 英里半径。所以使用$geoNear 确实可以更好地表达这一点,而且它的选项实际上允许你做你想做的事。

User.aggregate([
   "$geoNear": 
    "near": 
      "type": "Point",
      "coordinates": [76.7786787, 30.7352527]
    ,
    "spherical": true,
    "distanceField": "distance",
    "distanceMultiplier": 0.000621371,
    "maxDistance": 7 * 1609.34,
    "includeLocs": "location"
  ,
   "$addFields": 
    "parking_space": 
      "$filter": 
        "input": "$parking_space",
        "cond": 
          "$eq": ["$location", "$$this.location"]
        
      
    
  
],function(err,park_places) 
  // rest of your code.
)

这将产生如下所示的结果:


        "_id" : "5ae04fd45f104a5980cf7e0e",
        "name" : "Rehan",
        "email" : "rehan@gmail.com",
        "status" : true,
        "created_at" : "2018-04-25T09:52:20.266Z",
        "parking_space" : [
                
                        "_id" : "5ae06d4d5f104a5980cf7e52",
                        "parking_name" : "my space 2",
                        "restriction" : "no",
                        "hourly_rate" : "6",
                        "location" : 
                                "type" : "Point",
                                "coordinates" : [
                                        76.7786787,
                                        30.7352527
                                ]
                        
                
        ],
        "distance" : 0,
        "location" : 
                "type" : "Point",
                "coordinates" : [
                        76.7786787,
                        30.7352527
                ]
        


        "_id" : "5ae2f8148d51db4937b9df02",
        "name" : "nellima",
        "email" : "neel@gmail.com",
        "status" : true,
        "created_at" : "2018-04-27T10:14:44.598Z",
        "parking_space" : [
                
                        "_id" : "5ae2f89d8d51db4937b9df04",
                        "parking_name" : "my space 3",
                        "restriction" : "no",
                        "hourly_rate" : "60",
                        "location" : 
                                "type" : "Point",
                                "coordinates" : [
                                        76.7786787,
                                        30.7352527
                                ]
                        
                
        ],
        "distance" : 0,
        "location" : 
                "type" : "Point",
                "coordinates" : [
                        76.7786787,
                        30.7352527
                ]
        

我们在聚合管道中使用了两个阶段,因此要解释每个阶段的实际作用:

首先,$geoNear 在给定此处以 GeoJSON 格式提供的位置的情况下执行查询,以便在 "near" 选项中进行比较,这当然是主要约束。 "spherical" 选项通常是 "2dsphere" 索引所必需的,这是您实际希望用于数据的索引类型。 "distanceField" 是另一个强制参数,它指定属性的名称,该名称将实际记录与“文档”匹配位置的查询点的距离。

其他选项是使此功能适用于您想要在此处执行的操作的部分。首先是"distanceMultiplier",它在这里实际上是“可选的”,因为它只是控制将在"distanceField" 指定的属性中输出的值。我们在这里使用的值会将作为“距离”返回的 调整为 英里,这就是您通常看到的。这实际上对其余选项没有任何其他影响,但由于"distanceField" 是强制性的,我们希望显示一个“预期”数值。

下一个选项是模拟您的$geoWithin 语句的另一个主要“过滤器”。 "maxDistance" 选项设置匹配位置的“距离”上限。在这种情况下,我们将 7 乘以 1609.34 的英里数,即 英里 中有多少 。注意"distanceMultiplier" 对这个数字没有影响,所以任何“转换”必须在这里完成。

这里的最后一个选项是"includeLocs",这实际上是这里除了距离限制之外最重要的选项。这是告诉我们实际用于文档中包含的位置数组中的“最近匹配”的“位置数据”的实际部分。这里定义的当然是用于在从该管道阶段返回的文档中存储此数据的属性。您可以看到添加到每个文档的附加 "location" 属性反映了这一点。

因此,管道阶段实际上已经识别出匹配的"location" 数据,但这实际上并没有明确地识别出实际匹配的数组成员。因此,为了真正返回特定数组成员的信息,我们可以使用$filter 进行比较。

操作当然是简单的比较“匹配的位置”与每个数组成员的实际“位置”数据。由于只有 一个 匹配,您可以交替使用 $indexOfArray$arrayElemAt 之类的东西进行比较并仅提取“单个”结果,但 $filter 通常是最操作一目了然,通俗易懂。


半径限制的全部要点可以通过对条件的一些简短更改来证明。因此,如果我们将位置稍微移开:

   "$geoNear": 
    "near": 
      "type": "Point",
      "coordinates": [76.7786787, 30.6352527]    // <-- different location
    ,
    "spherical": true,
    "distanceField": "distance",
    "distanceMultiplier": 0.000621371,
    "maxDistance": 7 * 1609.34,
    "includeLocs": "location"
  ,

这仍在 半径 内,如为 "distanceField" 指定的输出中报告的那样:

 "distance" : 6.917030204982402,

但如果您将 半径 更改为小于报告的数字:

   "$geoNear": 
    "near": 
      "type": "Point",
      "coordinates": [76.7786787, 30.6352527]    // <-- different location
    ,
    "spherical": true,
    "distanceField": "distance",
    "distanceMultiplier": 0.000621371,
    "maxDistance": 6.91 * 1609.34,      // <--- smaller radius
    "includeLocs": "location"
  ,

那么查询将不会返回问题中提供的任何文档。因此,您可以看到此设置如何管理与使用 $geoWithin 查询实现的边界相同的边界,当然我们现在可以识别匹配的子文档。


多个匹配

作为关于该主题的最后一点说明,我们可以看到如何使用"includeLocs" 选项来识别父文档数组中某个位置的匹配条目。虽然这应该适合这里的用例,但明显的限制是在一个范围内匹配 多个 位置。

因此,“多个”匹配完全超出了$geoNear 或其他 MongoDB 地理空间操作的范围。另一种情况是 $unwind 在初始 $geoNear 之后的数组内容,然后是 $geoWithin 阶段,以便“过滤”那些 multiple 匹配:

User.aggregate([
   "$geoNear": 
    "near": 
      "type": "Point",
      "coordinates": [76.7786787, 30.7352527]
    ,
    "spherical": true,
    "distanceField": "distance",
    "distanceMultiplier": 0.000621371,
    "maxDistance": 7 * 1609.34,
  ,
   "$unwind": "$parking_space" ,
   "$match": 
    "parking_space.location": 
      "$geoWithin": 
        "$centerSphere": [
          [76.7786787, 30.7352527], 7 / 3963.2
        ]
      
    
  
],function(err,park_places) 
  // rest of your code.
)

在这里实际使用$geoNear 阶段可能会更好,我们实际上只是做同样的事情而不需要"includeLocs" 选项。但是,如果您真的想这样做,那么只需在 $unwind 阶段的任一侧使用 $geoWithin 就没有错:

User.aggregate([
   "$match": 
    "parking_space.location": 
      "$geoWithin": 
        "$centerSphere": [
          [76.7786787, 30.7352527], 7 / 3963.2
        ]
      
    
  
   "$unwind": "$parking_space" ,
   "$match": 
    "parking_space.location": 
      "$geoWithin": 
        "$centerSphere": [
          [76.7786787, 30.7352527], 7 / 3963.2
        ]
      
    
  
],function(err,park_places) 
  // rest of your code.
)

这没问题的原因是,虽然$geoWithin 工作得最“最佳”,但它实际上可以使用集合中定义的地理空间索引,但它实际上不需要 索引以返回结果。

因此,无论哪种情况,在“初始查询”返回包含 至少一个条件匹配的“文档”之后,我们只需 $unwind 数组内容,然后应用相同的约束再次过滤掉那些数组条目,现在作为文档。如果您希望返回“数组”,那么您始终可以将$group$push 的元素重新转换为数组形式。

相比之下,$geoNear 流水线阶段必须用作第一个流水线阶段。那是它唯一可以使用索引的地方,因此不能在以后使用它。但当然,“最近距离”信息可能对您有用,因此值得实际包含在查询结果和条件中。

【讨论】:

嘿,感谢您帮助我,但是当我尝试通过 GeoNear 获取结果时出现此错误 _geoNear command failed: ok: 0.0, errmsg: \"no geo index for geoNear_ 否则它可以正常工作地理范围内。 @Priyanklohan 这正是答案文本谈到创建"2dsphere" 索引并确保您在查询中实际使用索引的原因。所以如果你没有一个,你真的应该创建一个。索引创建实际上是从答案中提供的操作员文档链接链接的。 我已经在我的架构中定义了“2dsphere”,就像这样。 'User.plugin(passportLocalMongoose, usernameField: 'email' ); User.index( "parking_space.$.location": '2dsphere' ); User.pre('save', function(next) now = new Date(); this.updated_at = now; next(); );` 也许它没有像您在 $geoWitnin 的回答中解释的那样创建索引不需要索引。 @Priyanklohan "parking_space.location": '2dsphere' 我强烈建议您实际阅读已经提供的大量文档链接。也进入你的外壳并“删除”集合中的所有索引。像您所做的那样定义不正确的索引需要被删除。 Mongoose 将在下次应用程序启动时创建新索引。【参考方案2】:
User.aggregate([
        
            path: '$parking_space',
            preserveNullAndEmptyArrays: true
        ,
         $geoNear: 
          near:  type: 'Point', 'parking_space.location.coordinates': [76.7786787, 30.7352527] ,
          distanceField: 'dist',
          maxDistance: 7 / 3963.2,
          spherical: true
         ,
])

【讨论】:

以上是关于如何从 mongodb 通过 Geowithin 获取匹配的子文档?的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB:使用 $geoWithin 运算符没有得到正确的结果

MongoDB:使用$ geoWithin运算符无法获得正确的结果

地理空间查询是不是适用于数组? ($geoWithin, $geoIntersects)

我应该使用哪个索引进行 $geoWithin 搜索?

geoWithin查询 多边形查询

C#MongoDb api - Geojson几何存储成一个类