使用 $near 按存储在文档中的距离过滤文档

Filter Documents by Distance Stored in Document with $near

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

我在地图上有一组点(用户),收集模式如下

{
  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 索引。

在不将查询嵌入 $where: { 的情况下进行尝试。 $where operator 用于将 javascript 函数传递给数据库,您似乎不想在这里这样做(事实上,出于性能和安全原因,您通常应该避免这样做)。与位置无关。

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

假设您已经在收到事件数据并将其放在手边时对事件数据采取行动(如果您还没有,那是另一个问题,但请看 tailable cursors ),那么您应该有一个包含该数据的对象来查询用户。

因此,这不是 JavaScript 使用聚合框架进行 $where, as it cannot access the query data returned from a $near operation anyway. What you want instead is $geoNear 评估的情况。这可以投影从查询中找到的 "distance",并允许稍后阶段根据用户存储的值 "filter" 结果,以达到他们想要前往已发布事件的最大距离:

// 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
  }
)

现在您确实需要注意返回的数字,因为您似乎存储在 "legacy coordinate pairs" 而不是 GeoJSON 中,因此从该操作返回的距离将以弧度表示,而不是标准距离.因此,假设您存储在用户对象的 "miles" 或 "kilometers" 中,那么您需要通过手册中提到的 "Calculate Distances Using Spherical Geometry" 下手册中提到的公式进行计算。

基础知识是您需要除以地球赤道半径,即 3,963.2 英里或 6,378.1 公里,以便与您存储的内容进行比较。

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

假设 "kilometers" "if" 行变为:

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

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

另一件需要注意的事情是 $geoNear 默认有 100 个结果 "limit",所以你需要 "pump up" 那里的 "limit" 参数预期用户可能匹配的数量。对于一个非常大的系统,您甚至可能希望在用户 ID 的 "range lists" 中执行此操作,但您可以在单个聚合操作中达到内存允许的最大大小,并可能在需要的地方添加 allowDiskUse

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

如前所述,这里的重点是返回距离进行比较,所以下一阶段是$redact操作,可以根据返回的事件距离来拟合用户自己的"travel distance"值。最终结果只给出那些符合通知资格的用户,这些用户与事件的距离限制在他们自己的范围内。

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

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