在Mongodb中分组最近的位置

时间:2019-04-03 07:49:36

标签: mongodb geolocation aggregation-framework aggregate geonear

位置点另存为

{
  "location_point" : {
  "coordinates" : [ 
      -95.712891, 
      37.09024
  ],
  "type" : "Point"
  },
  "location_point" : {
  "coordinates" : [ 
      -95.712893, 
      37.09024
  ],
  "type" : "Point"
  },
  "location_point" : {
  "coordinates" : [ 
      -85.712883, 
      37.09024
  ],
  "type" : "Point"
  },
  .......
  .......
}

有几个文档。我需要group在最近的位置。 分组后,第一个Econd位置将在一个文档中,第三个在第二个文档中。 请不要使第一和第二的位置点不相等。两者都是最近的地方。

有什么办法吗?预先感谢。

1 个答案:

答案 0 :(得分:1)

快速而懒惰的解释是同时使用$geoNear$bucket聚合管道阶段来获得结果:

.aggregate([
    {
      "$geoNear": {
        "near": {
          "type": "Point",
          "coordinates": [
            -95.712891,
            37.09024
          ]
        },
        "spherical": true,
        "distanceField": "distance",
        "distanceMultiplier": 0.001
      }
    },
    {
      "$bucket": {
        "groupBy": "$distance",
        "boundaries": [
          0, 5, 10, 20,  50,  100,  500
        ],
        "default": "greater than 500km",
        "output": {
          "count": {
            "$sum": 1
          },
          "docs": {
            "$push": "$$ROOT"
          }
        }
      }
    }
])

更长的形式是,您可能应该了解解决问题的方式的“为什么?” 部分,并且甚至可以理解,即使这确实适用了至少一个仅在中引入的聚合运算符最新的MongoDB版本,实际上可以追溯到MongoDB 2.4。

使用$ geoNear

在任何“分组”中要查找的主要内容基本上是将"distance"字段添加到“附近”查询的结果中,指示该结果与搜索中使用的坐标有多远。幸运的是,这正是$geoNear聚合管道阶段所做的。

基本阶段是这样的:

{
  "$geoNear": {
    "near": {
      "type": "Point",
      "coordinates": [
        -95.712891,
        37.09024
      ]
    },
    "spherical": true,
    "distanceField": "distance",
    "distanceMultiplier": 0.001
  }
},

此阶段有三个必须提供的强制参数:

  • 附近-是用于查询的位置。既可以采用旧式坐标对形式,也可以采用GeoJSON数据形式。基本上,在计量表中都会考虑使用GeoJSON作为结果,因为这是GeoJSON标准。

  • 球形-必填,但实际上仅当索引类型为2dsphere时。它的默认值为false,但您可能确实希望对地球表面上的任何实际Geolocation数据使用2dsphere索引。

  • distanceField -这也是始终必需的,它是要添加到文档中的字段名称,其中包含通过near到查询位置的距离。此结果将以弧度或米为单位,具体取决于near参数中使用的数据格式的类型。如下所述,结果还受 optional 参数的影响。

可选参数为:

  • distanceMultiplier -这会将命名字段路径中的结果更改为distanceField乘数将应用于返回值,并可用于将“转换” 单位转换为所需格式。

      

    注意:distanceMultiplier确实适用于其他可选参数,例如maxDistanceminDistance。应用于这些可选参数的约束必须采用原始返回的单位格式。因此,使用GeoJSON时,无论您将distanceMultiplier值转换为km还是{来转换,设置为“最小”或“最大”距离的任何边界都需要计算为 {1}}。

要做的主要事情就是简单地以最接近最远的顺序返回“最近”文档(默认情况下最多100个),并包含名为{{1 }}在现有文档的内容中,这就是前面提到的实际输出,它允许您“分组”。

这里的miles只是将GeoJSON的默认转换为公里以进行输出。如果要在输出中英里,则可以更改乘数。即:

distanceField

这是完全可选的,但是您需要知道在下一个“分组” 阶段要应用什么单位(已转换或未转换):


实际的“分组” 可以归结为三个不同的选项,具体取决于您可用的MongoDB和您的实际需求:

选项1-$ bucket

MongoDB 3.4中添加了$bucket流水线阶段。它实际上是该版本中添加的几个“管道阶段” 之一,实际上更像是宏函数速记用于编写管道阶段和实际运算符的组合。稍后再讨论。

主要的基本参数是distanceMultiplier表达式,"distanceMultiplier": 0.000621371 ,它指定“分组”范围的 范围,以及{只要与groupBy表达式匹配的数据不在由boundaries定义的条目之间,则{1}}选项基本上用作输出中的*“ grouping key”或default字段。

_id

另一部分是groupBy,它基本上包含与$group一起使用的累加器表达式,并且实际上应该为您指示此{{3 }}实际上扩展到。那些按“分组键”进行实际的“数据收集”。

虽然有用,但$bucket有一个小缺陷,因为boundaries输出将永远是 { "$bucket": { "groupBy": "$distance", "boundaries": [ 0, 5, 10, 20, 50, 100, 500 ], "default": "greater than 500km", "output": { "count": { "$sum": 1 }, "docs": { "$push": "$$ROOT" } } } } output选项中定义的值,其中数据超出_id约束。如果您想要某些“ nicer” ,通常可以在客户端对结果进行后期处理,例如:

boundaries

这将用更有意义的“字符串”替换返回的default字段中的所有纯数字值,这些字符串描述了实际分组的内容。

请注意,尽管boundaries“可选” ,但是如果任何数据不在边界范围内,您将收到硬错误。实际上,返回的非常具体的错误将导致我们进入下一种情况。

选项2-$ group和$ switch

根据上述内容,您可能已经意识到,$bucket管道阶段的“宏翻译” 实际上变成了$bucket阶段,并且专门应用了$group运算符,作为result = result .map(({ _id, ...e }) => ({ _id: (!isNaN(parseFloat(_id)) && isFinite(_id)) ? `less than ${bounds[bounds.indexOf(_id)+1]}km` : _id, ...e }) ); 字段进行分组的参数。再次在MongoDB 3.4中引入了$switch运算符。

本质上,这实际上是上面使用$switch显示的 manual 构造,对_id字段的输出进行了一些微调,而对default字段的输出进行了一些微调 terse 带有前者产生的表达式。实际上,您可以使用聚合管道的“解释”输出来查看与以下清单类似的内容,但使用上面定义的管道阶段:

_id

实际上,除了更清晰的“标签” 之外,唯一的实际区别是$bucket在每个{上使用$bucket表达式和$gte {1}}。由于$lte的实际工作方式以及逻辑条件“落入” 的方式,就像在_id逻辑块的通用语言对应用法中那样,这不是必需的。

这实际上是关于个人喜好的一个问题,您是否更满意地在{ "$group": { "_id": { "$switch": { "branches": [ { "case": { "$and": [ { "$lt": [ "$distance", 5 ] }, { "$gte": [ "$distance", 0 ] } ] }, "then": "less than 5km" }, { "case": { "$and": [ { "$lt": [ "$distance", 10 ] } ] }, "then": "less than 10km" }, { "case": { "$and": [ { "$lt": [ "$distance", 20 ] } ] }, "then": "less than 20km" }, { "case": { "$and": [ { "$lt": [ "$distance", 50 ] } ] }, "then": "less than 50km" }, { "case": { "$and": [ { "$lt": [ "$distance", 100 ] } ] }, "then": "less than 100km" }, { "case": { "$and": [ { "$lt": [ "$distance", 500 ] } ] }, "then": "less than 500km" } ], "default": "greater than 500km" } }, "count": { "$sum": 1 }, "docs": { "$push": "$$ROOT" } } } 语句中为case定义输出“ strings”,或者是否可以使用后期处理值来重新格式化这样的内容。

无论哪种方式,它们基本上都返回与我们的第三个选项相同的输出(除了对switch结果定义了 order 之外)。

选项3-$ group和$ cond

如上所述,以上所有内容基本上都是基于$switch运算符的,但是就像在各种编程语言实现中所对应的一样,“ switch语句”实际上只是一种更简洁,更方便的编写{{1} } 等等。 MongoDB还有一个_id表达式,可以追溯到$switch的MongoDB 2.2:

case

同样,它们实际上都是相同的,主要的区别在于,代替{干净的数组}来处理为“案例”的选项,您拥有的是一组嵌套条件,其中$bucket仅仅包含另一个$cond,直到找到“边界”的末尾,然后if .. then .. else if ...仅包含if .. then .. else值。

由于我们至少还“假装” ,所以我们可以追溯到MongoDB 2.4(这是实际与$cond一起运行的约束,然后是其他类似{{ 1}}在该版本中不可用,因此您只需命名文档的所有字段表达式即可,以使用$geoNear添加该内容。


代码生成

所有这些实际上应该归结为“分组”实际上是由$push完成的,除非您需要对输出进行一些自定义或者您的MongoDB版本不支持,否则可能就是您要使用的分组它(尽管目前您可能不应该在3.4下运行任何MongoDB)。

当然,任何其他形式的语法都更长,但实际上可以使用相同的参数数组来本质上生成和运行上面显示的任何一种形式。

下面的示例清单(针对NodeJS)展示了,实际上,这是一个简单的过程,只需从{ "$group": { "_id": { "$cond": [ { "$and": [ { "$lt": [ "$distance", 5 ] }, { "$gte": [ "$distance", 0 ] } ] }, "less then 5km", { "$cond": [ { "$and": [ { "$lt": [ "$distance", 10 ] } ] }, "less then 10km", { "$cond": [ { "$and": [ { "$lt": [ "$distance", 20 ] } ] }, "less then 20km", { "$cond": [ { "$and": [ { "$lt": [ "$distance", 50 ] } ] }, "less then 50km", { "$cond": [ { "$and": [ { "$lt": [ "$distance", 100 ] } ] }, "less then 100km", "greater than 500km" ] } ] } ] } ] } ] }, "count": { "$sum": 1 }, "docs": { "$push": { "_id": "$_id", "location_point": "$location_point", "distance": "$distance" } } } } 的简单数组中生成所有数据即可进行分组,甚至只有几个定义的选项都可以在流水线操作以及任何客户端预处理或后处理中重新使用,以生成流水线指令,或将返回的结果处理为“更漂亮” 输出格式。

else

和示例输出(当然,上面的所有清单都是从此代码生成的):

else