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

时间:2015-10-02 13:42:58

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

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

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

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

2 个答案:

答案 0 :(得分: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”行成为“km”:

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

可靠地将您存储的千米值与重新调整的弧度值进行比较。

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

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

如上所述,这里的要点是返回比较距离,因此下一阶段是$redact操作,它可以根据事件返回的距离来调整用户自己的“行程距离”值。最终结果仅为那些符合自己距离约束的用户提供了有资格获得通知的事件。

这就是逻辑。您投影从用户到事件的距离,然后与用户存储的值进行比较,以确定他们准备旅行的距离。没有JavaScript,以及所有使其速度非常快的本地运营商。

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

答案 1 :(得分:0)

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

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