创建地理索引并查询附近

Create Geo Index and query near

我正在使用 azure cosmos db 如何在 C# 中创建索引并查询最近的前 10 个用户,如果文档是这样的:

{
  "_id" : "146138792054898475572",
  "email" : "abc@gmail.com",
  "firstName" : "abc",
  "lastName" : "abc",
  "loc" : {
    "lat" : 31.5200788,
    "lng" : 74.3236112
  },
  "gender" : "Male",
  "deviceId" : "YWg8crAjZLCrV",
  "createdDate" : ISODate("2017-06-11T11:35:41.601Z"),
  "updatedDate" : ISODate("2017-06-17T17:33:10.743Z")
}

查询:

db.User.find(
   {
      "loc":
        { $near :
           {
             $geometry: { type: "Point",  coordinates: [ 31.5200788, 74.3236112 ] },
             $minDistance: 1000,
             $maxDistance: 5000
           }
        }
   }
)

你的坐标落后了。每 MongoDB docs:

If you use longitude and latitude, specify coordinates in order of: longitude, latitude.

您需要反转坐标:

db.User.find(
   {
      "loc":
        { $near :
           {
             $geometry: { type: "Point",  coordinates: [ 74.3236112, 31.5200788 ] },
             $minDistance: 1000,
             $maxDistance: 5000
           }
        }
   }
)

此外,在您的 loc 属性 中,顺序是经度、纬度(您的顺序在您显示的文档中是相反的)。

这里有几个问题主要与您存储文档的方式有关。 MongoDB 支持以三种格式之一存储坐标点:

  • 遗留坐标对作为数组 其中数据按经度顺序和纬度顺序列出

    [ 74.3236112, 31.5200788 ]
    
  • 作为对象的遗留坐标对其中数据可以通过命名键组织,但这些必须明确排序和命名为 "lon""lat" 分别 "in order":

    { "lon": 74.3236112, "lat": 31.5200788 }
    
  • As GeoJSON 格式 其中存储的数据可以是任何有效的 GeoJSON 对象格式。对于 "Point" 类型,这是:

    {
      "type": "Point",
      "coordinates": [ 74.3236112, 31.5200788 ]
    }
    

为了演示,我有一个包含四种不同格式的示例文档的集合。我们将使用 .createIndex({ "loc": "2dsphere" }) 在该集合上创建一个索引,然后使用聚合管道 $geoNear 查询和 return 到查询点的实际距离:

db.geotest.aggregate([
  { "$geoNear": {
    "near": {
      "type": "Point",
      "coordinates": [ 74.3236112, 31.5200788 ]
    },
    "spherical": true,
    "distanceField": "distance"
  }}
])

显示四种不同的格式和从查询计算的距离。注意只有两个"valid"格式return距离查询位置0的正确距离:

{
        "_id" : ObjectId("5945f800b4051c7e52c90d1c"),
        "email" : "abc@gmail.com",
        "firstName" : "abc",
        "lastName" : "abc",
        "loc" : {
                "type" : "Point",
                "coordinates" : [
                        74.3236112,
                        31.5200788
                ]
        },
        "gender" : "Male",
        "deviceId" : "YWg8crAjZLCrV",
        "createdDate" : ISODate("2017-06-11T11:35:41.601Z"),
        "updatedDate" : ISODate("2017-06-17T17:33:10.743Z"),
        "distance" : 0
}
{
        "_id" : ObjectId("5945f8f6b4051c7e52c90d1e"),
        "email" : "abc@gmail.com",
        "firstName" : "abc",
        "lastName" : "abc",
        "loc" : {
                "lon" : 74.3236112,
                "lat" : 31.5200788
        },
        "gender" : "Male",
        "deviceId" : "YWg8crAjZLCrV",
        "createdDate" : ISODate("2017-06-11T11:35:41.601Z"),
        "updatedDate" : ISODate("2017-06-17T17:33:10.743Z"),
        "distance" : 0
}
{
        "_id" : "146138792054898475572",
        "email" : "abc@gmail.com",
        "firstName" : "abc",
        "lastName" : "abc",
        "loc" : {
                "lat" : 31.5200788,
                "lng" : 74.3236112
        },
        "gender" : "Male",
        "deviceId" : "YWg8crAjZLCrV",
        "createdDate" : ISODate("2017-06-11T11:35:41.601Z"),
        "updatedDate" : ISODate("2017-06-17T17:33:10.743Z"),
        "distance" : 5315650.25629941
}
{
        "_id" : ObjectId("5945f8b7b4051c7e52c90d1d"),
        "email" : "abc@gmail.com",
        "firstName" : "abc",
        "lastName" : "abc",
        "loc" : {
                "lat" : 31.5200788,
                "lon" : 74.3236112
        },
        "gender" : "Male",
        "deviceId" : "YWg8crAjZLCrV",
        "createdDate" : ISODate("2017-06-11T11:35:41.601Z"),
        "updatedDate" : ISODate("2017-06-17T17:33:10.743Z"),
        "distance" : 5315650.25629941
}

所以为了能够正确查询,需要"valid"格式的索引。我个人建议使用 GeoJSON 格式,因为它被广泛用作标准,并且还为您提供了存储任何有效 GeoJSON 对象的选项,而不仅仅是 "point coordinates".

您可以使用如下操作转换数据:

var ops = [];

db.User.find({ 
  "loc.lng": { "$exists": true }, 
  "loc.lat": { "$exists": true }
}).forEach(function(doc) {
  ops.push({
    "updateOne": {
      "filter": { "_id": doc._id },
      "update": {
        "$set": {
          "loc": {
            "type": "Point",
            "coordinates": [ doc.loc.lng, doc.loc.lat ]
          }
        }
      }
    }
  });
  if ( ops.length >= 1000 ) {
    db.User.bulkWrite(ops);
    ops = [];
  }
});

if ( ops.length > 0 ) {
  db.User.bulkWrite(ops);
  ops = [];
}

这会将 "loc" 数据重写为正确的格式,以便索引和查询正常工作。

实际上,您应该在更新之前对集合执行 .dropIndexes() 以节省写入成本,然后在完成后重新创建索引。这不是必需的步骤,但建议这样做。

N.B 此外,这里的 $minDistace 参数经过更正后的数据实际上仍会排除结果,因为正如所证明的那样,距查询点的实际距离(是同坐标)是0。因此,删除对测试的限制,或者更好地使用 $geoNear 进行测试,如图所示。

另请注意 提到了不支持聚合管道协议的 DocumentDB。此处的示例可以毫无问题地针对 MongoDB 工作,主要用于演示数据的 "format" 问题。常规 $nearother geospatial general queries are supported。但数据必须采用正确的受支持格式。