如何从Geongithb获取Geowithin匹配的子文档?

时间:2018-04-27 11:21:49

标签: node.js mongodb mongoose mongodb-query geospatial

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

{
         "_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
                    ]
                },
            }
        ],
    },
   }

是否可以获得这样的数据。

2 个答案:

答案 0 :(得分:5)

对于您在此处尝试执行的操作,更好的选择是实际使用$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",实际上是#34;可选&#34;这里因为它只是控制将在"distanceField"指定的属性中输出的值。我们在这里使用的值将调整返回的作为&#34;距离&#34;进入里程,这是你通常看到的。这实际上对其他选项没有任何其他影响,但由于"distanceField"是强制性的,我们希望显示&#34;期望&#34; 数值。< / p>

下一个选项是另一个主要&#34;过滤器&#34;模仿你的$geoWithin陈述。 "maxDistance"选项设置了一个上限&#34;距离多远&#34;匹配的位置可以。在这种情况下,我们将7给予我们乘以1609.34的里程,即英里的数量。请注意,"distanceMultiplier"对此数字没有任何影响,因此任何&#34;转换&#34; 必须也可以在这里完成。

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

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

当然,操作是对匹配位置的简单比较&#34;到实际的#34;位置&#34;每个阵列成员的数据。由于只有一个匹配,您可以使用$indexOfArray$arrayElemAt之类的内容进行比较,并仅提取&#34;单个&#34;结果,但$filter通常是最容易理解的解释性操作。

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

  { "$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,

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

  { "$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"选项如何用于标识父文档数组中位置的匹配条目。虽然这应该适合这里的用例,但明显的限制是匹配范围内的 mutliple 位置。

所以&#34;多个&#34;匹配仅仅超出了$geoNear或MongoDB的其他地理空间操作的范围。另一种情况是$unwind数组内容,而不是初始$geoNear,然后是$geoWithin阶段,以便&#34;过滤&#34;那些多个匹配:

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"选项。但是,如果您真的想要,那么在$geoWithin阶段的任何一侧使用$unwind都没有错:

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最适合&#34;最佳地&#34; 当它实际上可以使用在集合上定义的地理空间索引时,它实际上不需要索引才能返回结果。

因此,在&#34;初始查询&#34;之后的任何一种情况下;返回&#34;文件&#34;其中包含至少一个匹配条件,我们只需$unwind数组内容,然后再次应用相同的约束来过滤掉那些数组条目,现在作为文档。如果你想要&#34;数组&#34;要返回,您可以随时将$group$push元素恢复为数组形式。

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

答案 1 :(得分:1)

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
        } },
])