$geoNear
起因
在开发的时候碰到一个情况,数据源有100个景点,需要对给出的一个点,求距离它最近的景点,并且在前端下拉翻页的过程中,依距离顺序依次显示出来。大众点评做出了这个效果,但是以我现阶段的经验,想不出比较完美的解决方案。尤其是处理下拉翻页还能保证排序的效果,这一点是需要先对所有的点求距离,然后将所有点进行排序然后输出吗?这样效率不低吗?后来想到,mongodb有一个查询操作是geoNear,于是思考通过这种方案来解决排序效率的问题。
过程
在网上查了一下,大概需要三步:
- 确保有
loc:[longitude, latitude]属性 - 给
loc增加索引AttractionSchema.index({loc: '2d'}); 使用
geoNearModel.geoNear([1,3], { maxDistance : 5, spherical : true }, function(err,results, stats) {
console.log(results);
});
或者确保有
确保有GeoJSON数据结构
{ <location field>: { type: "<GeoJSON type>" , coordinates: <coordinates> } }
- 增加索引
AttractionSchema.index({location: '2dsphere'}); 查找
// geoJson
var point = { type : "Point", coordinates : [9,9] };
Model.geoNear(point, { maxDistance : 5, spherical : true }, function(err, results, stats) {
console.log(results);
});
扩展
利用aggregate
models.Attraction.aggregate(db.runCommand(
[{
"$geoNear": {
// "near": {
// "type": "Point",
// "coordinates": [-74.00824900000001, 40.708635]
// },
"near": [-74.00824900000001, 40.708635],
"maxDistance": 10000,
"distanceMultiplier": 6371,
"spherical": true,
"query":{cityid: '516cc44ce3c6a60f69000011'},
"distanceField": "dist.calculated",
"includelocs":"dist.location"
}
},{ "$skip": 10
},{
"$limit": 10
}],
function(err, docs) {
if (err) {
console.log(err);
} else {
docs.forEach(function(element, index){
console.log(element.cityname + ' '+element.attractions + ' '+ element.dist.calculated);
});
}
// These are not mongoose documents, but you can always cast them
}
);
{
geoNear : "Infos" ,
near : { "type" : "Point" , "coordinates" : [113.643196,34.800495]} ,
spherical : true ,
minDistance: 0,
maxDistance: 5000,
num : 50
}) ===
mongoDB支持二维空间索引,使用空间索引,mongoDB支持一种特殊查询,如某地图网站上可以查找离你最近的咖啡厅,银行等信息。这个使用mongoDB的空间索引结合特殊的查询方法很容易实现。
前提条件:
建立空间索引的key可以使用array或内嵌文档存储,但是前两个elements必须存储固定的一对空间位置数值。如
{ loc : [ 50 , 30 ] }
{ loc : { x : 50 , y : 30 } }
{ loc : { foo : 50 , y : 30 } }
{ loc : { lat : 40.739037, long: 73.992964 } }
# 使用范例1:
> db.mapinfo.drop()
true
> db.mapinfo.insert({"category" : "coffee","name" : "digoal coffee bar","loc" : [70,80]})
> db.mapinfo.insert({"category" : "tea","name" : "digoal tea bar","loc" : [70,80]})
> db.mapinfo.insert({"category" : "tea","name" : "hangzhou tea bar","loc" : [71,81]})
> db.mapinfo.insert({"category" : "coffee","name" : "hangzhou coffee bar","loc" : [71,81]})
# 未创建2d索引时,不可以使用$near进行查询
> db.mapinfo.find({loc : {$near : [50,50]}})
error: {
"$err" : "can't find special index: 2d for: { loc: { $near: [ 50.0, 50.0 ] } }",
"code" : 13038
}
# 在loc上面创建2d索引
> db.mapinfo.ensureIndex({"loc" : "2d"},{"background" : true})
> db.mapinfo.getIndexes()
[
{
"name" : "_id_",
"ns" : "test.mapinfo",
"key" : {
"_id" : 1
}
},
{
"_id" : ObjectId("4d242e1f3238ba30f9ca05ad"),
"ns" : "test.mapinfo",
"key" : {
"loc" : "2d"
},
"name" : "loc_",
"background" : true
}
]
# 查询测试,返回结果按照从最近到最远的顺序排序输出.
> db.mapinfo.find({loc : {$near : [72,82]},"category" : "coffee"}).explain()
{
"cursor" : "GeoSearchCursor",
"nscanned" : 2,
"nscannedObjects" : 2,
"n" : 2,
"millis" : 0,
"indexBounds" : { }
}
> db.mapinfo.find({loc : {$near : [72,82]},"category" : "coffee"})
{ "_id" : ObjectId("4d242dce3238ba30f9ca05ac"), "category" : "coffee", "name" : "hangzhou coffee bar", "loc" : [ 71, 81 ] }
{ "_id" : ObjectId("4d242d8b3238ba30f9ca05a9"), "category" : "coffee", "name" : "digoal coffee bar", "loc" : [ 70, 80 ] }
# 换一个经纬度后结果相反.
> db.mapinfo.find({loc : {$near : [69,69]},"category" : "coffee"})
{ "_id" : ObjectId("4d242d8b3238ba30f9ca05a9"), "category" : "coffee", "name" : "digoal coffee bar", "loc" : [ 70, 80 ] }
{ "_id" : ObjectId("4d242dce3238ba30f9ca05ac"), "category" : "coffee", "name" : "hangzhou coffee bar", "loc" : [ 71, 81 ] }
# 2d默认取值范围[-179,-179]到[180,180] 包含这两个点,超出范围将报错
> db.mapinfo.insert({"category" : "bank","name" : "china people bank","loc" : [181,181]})
point not in range
> db.mapinfo.insert({"category" : "bank","name" : "china people bank","loc" : [-179,-180]})
in > 0
# 如果已经存在超过范围的值,建2D索引将报错
> db.mapinfo.insert({"category" : "bank","name" : "china people bank","loc" : [-180,-180]})
> db.mapinfo.ensureIndex({"loc" : "2d"})
in > 0
# 在建2d索引的时候可以指定取值范围
# 如,以上包含了[-180,-180]这个点之后,建2d索引将报错,使用以下解决.或者把这条记录先处理掉.
# 在限制条件下,min不包含,max包含,从下面建索引的语句中可以看出.
> db.mapinfo.ensureIndex({"loc" : "2d"},{min:-181,max:180})
> 成功
# 注意官方文档上说you can only have 1 geo2d index per collection right now,不过测试可以建多个,如下
> db.mapinfo.drop()
true
> db.mapinfo.insert({"category" : "bank","name" : "china people bank","loc" : [71,81],"HQ_loc" : [91,101]})
> db.mapinfo.ensureIndex({"loc" : "2d"},{"background" : "true"})
> db.mapinfo.ensureIndex({"HQ_loc" : "2d"},{"background" : "true"})
> db.mapinfo.getIndexes()
[
{
"name" : "_id_",
"ns" : "test.mapinfo",
"key" : {
"_id" : 1
}
},
{
"_id" : ObjectId("4d2439803238ba30f9ca05cd"),
"ns" : "test.mapinfo",
"key" : {
"loc" : "2d"
},
"name" : "loc_",
"background" : "true"
},
{
"_id" : ObjectId("4d2439863238ba30f9ca05ce"),
"ns" : "test.mapinfo",
"key" : {
"HQ_loc" : "2d"
},
"name" : "HQ_loc_",
"background" : "true"
}
]
> db.mapinfo.find({"loc" : {"$near" : [20,21]}})
{ "_id" : ObjectId("4d2439643238ba30f9ca05cc"), "category" : "bank", "name" : "china people bank", "loc" : [ 71, 81 ], "HQ_loc" : [ 91, 101 ] }
> db.mapinfo.find({"HQ_loc" : {"$near" : [20,21]}})
{ "_id" : ObjectId("4d2439643238ba30f9ca05cc"), "category" : "bank", "name" : "china people bank", "loc" : [ 71, 81 ], "HQ_loc" : [ 91, 101 ] } # 使用范例2:
# 测试数据
> db.mapinfo.find()
{ "_id" : ObjectId("4d2439643238ba30f9ca05cc"), "category" : "bank", "name" : "china people bank", "loc" : [ 71, 81 ], "HQ_loc" : [ 91, 101 ] }
{ "_id" : ObjectId("4d243a743238ba30f9ca05cf"), "category" : "coffee", "name" : "digoal coffee bar", "loc" : [ 100, 81 ], "HQ_loc" : [ 100, 101 ] }
{ "_id" : ObjectId("4d243a8b3238ba30f9ca05d0"), "category" : "tea", "name" : "digoal tea bar", "loc" : [ 110, 81 ], "HQ_loc" : [ 110, 101 ] }
{ "_id" : ObjectId("4d243ab23238ba30f9ca05d1"), "category" : "shop", "name" : "digoal supermarket", "loc" : [ 120, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aba3238ba30f9ca05d2"), "category" : "shop", "name" : "digoal supermarket1", "loc" : [ 121, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243abe3238ba30f9ca05d3"), "category" : "shop", "name" : "digoal supermarket2", "loc" : [ 122, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243ac33238ba30f9ca05d4"), "category" : "shop", "name" : "digoal supermarket3", "loc" : [ 123, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243ac83238ba30f9ca05d5"), "category" : "shop", "name" : "digoal supermarket4", "loc" : [ 124, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243ace3238ba30f9ca05d6"), "category" : "shop", "name" : "digoal supermarket5", "loc" : [ 125, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243ad63238ba30f9ca05d7"), "category" : "shop", "name" : "digoal supermarket6", "loc" : [ 126, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aee3238ba30f9ca05d8"), "category" : "shop", "name" : "digoal supermarket7", "loc" : [ 26, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243af43238ba30f9ca05d9"), "category" : "shop", "name" : "digoal supermarket8", "loc" : [ 27, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243af93238ba30f9ca05da"), "category" : "shop", "name" : "digoal supermarket9", "loc" : [ 29, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aff3238ba30f9ca05db"), "category" : "shop", "name" : "digoal supermarket10", "loc" : [ 30, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243b063238ba30f9ca05dc"), "category" : "shop", "name" : "digoal supermarket11", "loc" : [ 31, 81 ], "HQ_loc" : [ 120, 101 ] }
# 索引
> db.mapinfo.getIndexes()
[
{
"name" : "_id_",
"ns" : "test.mapinfo",
"key" : {
"_id" : 1
}
},
{
"_id" : ObjectId("4d2439803238ba30f9ca05cd"),
"ns" : "test.mapinfo",
"key" : {
"loc" : "2d"
},
"name" : "loc_",
"background" : "true"
},
{
"_id" : ObjectId("4d2439863238ba30f9ca05ce"),
"ns" : "test.mapinfo",
"key" : {
"HQ_loc" : "2d"
},
"name" : "HQ_loc_",
"background" : "true"
}
]
# 查询离[50,50]最近的5家商店
> db.mapinfo.find({"loc" : {"$near" : [50,50]},"category" : "shop"}).limit(5)
{ "_id" : ObjectId("4d243b063238ba30f9ca05dc"), "category" : "shop", "name" : "digoal supermarket11", "loc" : [ 31, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aff3238ba30f9ca05db"), "category" : "shop", "name" : "digoal supermarket10", "loc" : [ 30, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243af93238ba30f9ca05da"), "category" : "shop", "name" : "digoal supermarket9", "loc" : [ 29, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243af43238ba30f9ca05d9"), "category" : "shop", "name" : "digoal supermarket8", "loc" : [ 27, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aee3238ba30f9ca05d8"), "category" : "shop", "name" : "digoal supermarket7", "loc" : [ 26, 81 ], "HQ_loc" : [ 120, 101 ] }
# 找出限制离[50,50]在37 的商店,使用maxDistance
> db.mapinfo.find({"loc" : {"$near" : [50,50], "$maxDistance" : 37},"category" : "shop"})
{ "_id" : ObjectId("4d243b063238ba30f9ca05dc"), "category" : "shop", "name" : "digoal supermarket11", "loc" : [ 31, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aff3238ba30f9ca05db"), "category" : "shop", "name" : "digoal supermarket10", "loc" : [ 30, 81 ], "HQ_loc" : [ 120, 101 ] }
# 复合索引
> db.mapinfo.ensureIndex({"loc" : "2d","category" : 1})
> db.mapinfo.getIndexes()
[
{
"name" : "_id_",
"ns" : "test.mapinfo",
"key" : {
"_id" : 1
}
},
{
"_id" : ObjectId("4d2439803238ba30f9ca05cd"),
"ns" : "test.mapinfo",
"key" : {
"loc" : "2d"
},
"name" : "loc_",
"background" : "true"
},
{
"_id" : ObjectId("4d2439863238ba30f9ca05ce"),
"ns" : "test.mapinfo",
"key" : {
"HQ_loc" : "2d"
},
"name" : "HQ_loc_",
"background" : "true"
},
{
"_id" : ObjectId("4d243ce13238ba30f9ca05dd"),
"ns" : "test.mapinfo",
"key" : {
"loc" : "2d",
"category" : 1
},
"name" : "loc__category_1"
}
] 3. 范例 3
# 除了使用find来搜索以外,还可以使用runCommand
> db.runCommand({"geoNear" : "mapinfo","near" : [50,50],"num" : 10})
{ "errmsg" : "more than 1 geo indexes :(", "ok" : 0 }
# 这里报错,原因是mapinfo超过一个2d索引,但是使用find来查询不会报错,
# 只保留一个“2d"索引后,使用runCommand正常
> db.mapinfo.dropIndex({"loc" : "2d","category" : 1})
{ "nIndexesWas" : 4, "ok" : 1 }
> db.runCommand({"geoNear" : "mapinfo","near" : [50,50],"num" : 10})
{ "errmsg" : "more than 1 geo indexes :(", "ok" : 0 }
> db.mapinfo.dropIndex({"HQ_loc" : "2d"})
{ "nIndexesWas" : 3, "ok" : 1 }
# "num" 限制返回的记录数
# 使用runCommand和geoNear的好处是可以返回距离.本例"dis" : 36.3593194466869,
> db.runCommand({"geoNear" : "mapinfo","near" : [50,50],"num" : 1})
{
"ns" : "test.mapinfo",
"near" : "1100110000001111110000001111110000001111110000001111",
"results" : [
{
"dis" : 36.3593194466869,
"obj" : {
"_id" : ObjectId("4d243b063238ba30f9ca05dc"),
"category" : "shop",
"name" : "digoal supermarket11",
"loc" : [
31,
81
],
"HQ_loc" : [
120,
101
]
}
}
],
"stats" : {
"time" : 0,
"btreelocs" : 6,
"nscanned" : 7,
"objectsLoaded" : 3,
"avgDistance" : 36.3593194466869,
"maxDistance" : 36.3593194466869
},
"ok" : 1
}
# 使用runCommand同样也可以使用普通的FIND的限制条件,如下放在query : { "category" : "coffee" }
> db.runCommand({"geoNear" : "mapinfo","near" : [50,50],"num" : 1,query : { "category" : "coffee" }})
{
"ns" : "test.mapinfo",
"near" : "1100110000001111110000001111110000001111110000001111",
"results" : [
{
"dis" : 58.830266786369556,
"obj" : {
"_id" : ObjectId("4d243a743238ba30f9ca05cf"),
"category" : "coffee",
"name" : "digoal coffee bar",
"loc" : [
100,
81
],
"HQ_loc" : [
100,
101
]
}
}
],
"stats" : {
"time" : 0,
"btreelocs" : 15,
"nscanned" : 15,
"objectsLoaded" : 7,
"avgDistance" : 58.830266786369556,
"maxDistance" : 58.830266786369556
},
"ok" : 1
} 4. 范例4
# 空间索引还支持范围搜索,目前支持圆和矩阵的范围
# 使用box
> box = [[19,19],[90,90]]
[ [ 19, 19 ], [ 90, 90 ] ]
> db.mapinfo.find({"loc" : {"$within" : {"$box" : box}}})
{ "_id" : ObjectId("4d2439643238ba30f9ca05cc"), "category" : "bank", "name" : "china people bank", "loc" : [ 71, 81 ], "HQ_loc" : [ 91, 101 ] }
{ "_id" : ObjectId("4d243b063238ba30f9ca05dc"), "category" : "shop", "name" : "digoal supermarket11", "loc" : [ 31, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aff3238ba30f9ca05db"), "category" : "shop", "name" : "digoal supermarket10", "loc" : [ 30, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243af93238ba30f9ca05da"), "category" : "shop", "name" : "digoal supermarket9", "loc" : [ 29, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243af43238ba30f9ca05d9"), "category" : "shop", "name" : "digoal supermarket8", "loc" : [ 27, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aee3238ba30f9ca05d8"), "category" : "shop", "name" : "digoal supermarket7", "loc" : [ 26, 81 ], "HQ_loc" : [ 120, 101 ] }
# 使用center point and radius
> center = [29,81]
[ 29, 81 ]
> radius = 10
10
> db.mapinfo.find({"loc" : {"$within" : {"$center" : [center,radius]}}})
{ "_id" : ObjectId("4d243af93238ba30f9ca05da"), "category" : "shop", "name" : "digoal supermarket9", "loc" : [ 29, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243af43238ba30f9ca05d9"), "category" : "shop", "name" : "digoal supermarket8", "loc" : [ 27, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aff3238ba30f9ca05db"), "category" : "shop", "name" : "digoal supermarket10", "loc" : [ 30, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243b063238ba30f9ca05dc"), "category" : "shop", "name" : "digoal supermarket11", "loc" : [ 31, 81 ], "HQ_loc" : [ 120, 101 ] }
{ "_id" : ObjectId("4d243aee3238ba30f9ca05d8"), "category" : "shop", "name" : "digoal supermarket7", "loc" : [ 26, 81 ], "HQ_loc" : [ 120, 101 ] } 注意事项:
1. mongoDB处理的是平面距离,但是实际生活中如果涉及到大范围的距离搜索,可能会有偏差,因为地球是球型的。The current implementation assumes an idealized model of a flat earth, meaning that an arcdegree of latitude (y) and longitude (x) represent the same distance everywhere. This is only true at the equator where they are both about equal to 69 miles or 111km. However, at the 10gen offices at { x : -74 , y : 40.74 } one arcdegree of longitude is about 52 miles or 83 km (latitude is unchanged). This means that something 1 mile to the north would seem closer than something 1 mile to the east.
2. 2d索引目前还不支持sharding,In the meantime sharded clusters can use geospatial indexes for unsharded collections within the cluster.
3. New Spherical Model,1.7.0以后将引入新的空间模型. 其他:
The current implementation encodes geographic hash codes atop standard MongoDB b-trees. Results of $near queries are exact. The problem with geohashing is that prefix lookups don't give you exact results, especially around bit flip areas. MongoDB solves this by doing a grid by grid search after the initial prefix scan. This guarantees performance remains very high while providing correct results
随机推荐
- Linux下查看文件权限、修改文件权限的方法
查看权限命令查看目录的相关权限可以采用命令ls -lD,或者直接用ls -la 如 ls -l www.jb51.net //这里表示查看www.jb51.net目录 修改权限命令 chmod 77 ...
- JavaSE复习_10 多线程复习
△wait()和sleep()的区别: 1.wait():没有等待时间,而sleep()需要有等待时间作为参数. 2.在同步中对于CPU的执行权和锁的处理不同: wait()会释放执行权和锁. ...
- (一)linux常见命令
一.chmod 修改文件权限 每一文件或目录的访问权限都有三组,每组用三位表示,分别为文件属主的读.写和执行权限:与属主同组的用户的读.写和执行权限:系统中其他用户的读.写和执行权限.横线代表空许可. ...
- Java 13 字符串
1 String对象不可变 每一个修改String值的方法 实际上都是创建一个全新的String对象 public class Immutable { public static String upc ...
- linux 跨IP拷贝命令 scp
原文:http://blog.csdn.net/mexican_jacky/article/details/52847094 scp -r ROOT/ tms2api@10.230.4.215:/Ja ...
- 【服务器防护】iptables 配置详解(非常棒的案例)
一. iptables 基本命令使用举例 链的基本操作 1.清除所有的规则.1)清除预设表filter中所有规则链中的规则.# iptables -F2)清除预设表filter中使用者自定链中的规则. ...
- Bootstrap的粗体和斜体
一.粗体 粗体就是给文本加粗,在普通的元素中我们一般通过font-weight设置为bold关键词给文本加粗. 在Bootstrap中,可以使用<b>和<strong>标签让文 ...
- VC++编译zlib
目录 第1章简介 1 第2章版本1.2.3 2 2.1 编译汇编代码 2 2.1.1 32位汇编 2 2.1.2 64位汇编 5 2.2 Visual C++ 6.0 ...
- Mongos与集群均衡
版权声明:本文由孔德雨原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/190 来源:腾云阁 https://www.qclo ...
- 阿里Linux Shell脚本面试25个经典问答
转载: 阿里Linux Shell脚本面试25个经典问答 Q:1 Shell脚本是什么.它是必需的吗? 答:一个Shell脚本是一个文本文件,包含一个或多个命令.作为系统管理员,我们经常需要使用多个命 ...