创建地理索引并查询附近
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" 问题。常规 $near
和 other geospatial general queries are supported。但数据必须采用正确的受支持格式。
我正在使用 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" 问题。常规 $near
和 other geospatial general queries are supported。但数据必须采用正确的受支持格式。