【问题标题】:Filter Documents by Distance Stored in Document with $near通过 $near 存储在文档中的距离过滤文档
【发布时间】:2015-12-30 17:53:28
【问题描述】:

我正在使用以下示例来更好地解释我的需求。

我在地图上有一组点(用户),集合架构如下

{
  location:{
    latlong:[long,lat]
  },
  maxDistance:Number
}

我有另一个收藏在该地区发生的事件。架构如下所示

{
  eventLocation:{
    latlong:[long,lat]
  }
}

现在,用户可以添加他们的位置和他们想要前往的最大距离以参加活动并保存它。

每当发布新活动时,所有满足其偏好的用户都会收到通知。现在我该如何查询。我尝试对用户架构进行以下查询

{
  $where: {
    'location.latlong': {
      $near: {
        $geometry: {
          type: "Point",
          coordinates: [long,lat]
        },
        $maxDistance: this.distance
      }
    }
  }
}

出错了

error: {
    "$err" : "Can't canonicalize query: BadValue $where got bad type",
    "code" : 17287
}

我如何查询上述情况,因为 maxDistance 由用户定义且不固定。我正在使用 2dsphere 索引。

【问题讨论】:

    标签: mongodb mongoose mongodb-query geospatial aggregation-framework


    【解决方案1】:

    试试吧,不要在$where: { 中嵌入您的查询。 $where operator 用于将 javascript 函数传递给数据库,您似乎不想在这里执行此操作(实际上,出于性能和安全原因,您通常应该避免这样做)。它与位置无关。

    {
      'location.latlong': {
        $near: {
          $geometry: {
            type: "Point",
            coordinates: [long,lat]
          },
          $maxDistance: this.distance
        }
      }
    }
    

    【讨论】:

    • 你真的应该读过这个问题。因为其目的是将存储的“用户值”与他们想要前往事件的最大距离与找到的新发布事件的距离进行比较。这实际上只是对$where 的评论,是一个明显的错误,而不是理解所要求的内容。这是一个合理的问题,所做的只是指出错误但没有给出解决方案。
    【解决方案2】:

    假设您已经着手处理收到的事件数据并将其放在手中(如果您还没有,那么这是另一个问题,但请查看tailable cursors),那么您应该有一个对象用于查询用户的数据。

    因此,这不适用于使用 $where 进行 JavaScript 评估,因为无论如何它都无法访问从 $near 操作返回的查询数据。你想要的是来自聚合框架的$geoNear。这可以投影从查询中找到的“距离”,并允许稍后阶段根据用户存储的值“过滤”结果,以获得他们想要前往已发布事件的最大距离:

    // Represent retrieved event data
    var eventData = {
      eventLocation: {
        latlong: [long,lat]
      }
    };
    
    // Find users near that event within their stored distance
    User.aggregate(
      [
        { "$geoNear": {
          "near": {
            "type": "Point",
            "coordinates": eventData.eventLocation.latlong
          },
          "distanceField": "eventDistance",
          "limit": 100000,
          "spherical": true
        }},
        { "$redact": {
          "$cond": {
            "if": { "$lt": [ "$eventDistance", "$maxDistance" ] },
            "then": "$$KEEP",
            "else": "$$PRUNE"
          }
        }}
      ]
      function(err,results) {
        // Work with results in here
      }
    )
    

    现在您确实需要小心返回的数字,因为您似乎存储在“旧坐标对”而不是 GeoJSON 中,因此从该操作返回的距离将以弧度表示,而不是标准距离。因此,假设您在用户对象上存储为“英里”或“公里”,那么您需要通过手册中提到的公式在"Calculate Distances Using Spherical Geometry" 下进行计算,如手册中所述。

    基本原理是您需要除以地球的赤道半径,即 3,963.2 英里或 6,378.1 公里,以便与您存储的数据进行比较。

    替代方法是存储在 GeoJSON 中,其中以米为单位进行一致的测量。

    假设“如果”行变成“公里”:

    "if": { "$lt": [
        "$eventDistance",
        { "$divide": [ "$maxDistance", 6,378.1 ] }
     ]},
    

    可靠地将您存储的公里值与返回的弧度结果进行比较。

    要注意的另一件事是$geoNear 的默认“限制”为 100 个结果,因此您需要将“限制”参数“提高”到预期用户可能匹配的数量。对于一个非常大的系统,您甚至可能希望在用户 ID 的“范围列表”中执行此操作,但您可以在单个聚合操作中尽可能大地使用内存,并可能在需要时添加 allowDiskUse

    如果您不调整该参数,则只会返回最接近的 100 个结果(默认值),这甚至可能不适合您过滤那些“接近”事件开始的下一个操作。不过请使用常识,因为您肯定有一个最大距离,甚至可以过滤掉潜在用户,并且也可以将其添加到查询中。

    如上所述,这里的重点是返回距离进行比较,因此下一阶段是$redact 操作,它可以将用户自己的“旅行距离”值与返回的事件距离相匹配。最终结果只为那些在他们自己的距离范围内的用户提供了有资格获得通知的事件。

    这就是逻辑。您预测从用户到事件的距离,然后与用户存储的值进行比较,以了解他们准备行进的距离。没有 JavaScript,所有的原生运算符都让它变得非常快。

    也如选项和一般评论中所述,我确实建议您使用“2dsphere”索引进行准确的球面距离计算,以及转换为 GeoJSON 存储以存储数据库对象中的坐标,因为它们都是产生一致结果的通用标准。

    【讨论】:

    • 感谢您的解决方案,但 "if": { "$lt": [ "$maxDistance", "$eventDistance" ] }, 应该是 "if": { "$lt": [ "$eventDistance","$maxDistance" ] }, 对吧?
    • @raj 哎呀。你是对的。第一个值小于第二个值是运算符的工作方式。已更正。
    猜你喜欢
    • 2020-04-19
    • 1970-01-01
    • 1970-01-01
    • 2016-02-05
    • 1970-01-01
    • 1970-01-01
    • 2011-03-19
    • 2017-08-07
    • 2018-01-26
    相关资源
    最近更新 更多