$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
随机推荐
- (一)使用springAPI以及自定义类 实现AOP-aop编程
Spring的另一个重要思想是AOP,面向切面的编程,它提供了一种机制,可以在执行业务前后执行另外的代码,Servlet中的Filter就是一种AOP思想的体现,下面通过一个例子来感受一下. 假设我们 ...
- linux下的挂载点和分区是什么关系
Linux 使用字母和数字的组合来指代磁盘分区.这可能有些使人迷惑不解,特别是如果你以前使用“C 驱动器”这种方法来指代硬盘及它们的分区.在 DOS/Windows 的世界里,分区是用下列方法命名的: ...
- SVM(支持向量机)与统计机器学习 & 也说一下KNN算法
因为SVM和统计机器学习内容很多,所以从 http://www.cnblogs.com/charlesblc/p/6188562.html 这篇文章里面分出来,单独写. 为什么说SVM和统计学关系很大 ...
- 转:对TCP/IP网络协议的深入浅出归纳
转自:http://blog.jobbole.com/74795/ 前段时间做了一个开发,涉及到网络编程,开发过程比较顺利,但任务完成后始终觉得有一些疑惑.主要是因为对网络协议不太熟悉,对一些概念也没 ...
- OpenGL的几何变换[转]
OpenGL的几何变换 1.实验目的: 理解掌握一个OpenGL程序平移.旋转.缩放变换的方法. 2.实验内容: (1)阅读实验原理,运行示范实验代码,掌握OpenGL程序平移.旋转.缩放变换的方法: ...
- VB6 GDI+ 入门教程[4] 文字绘制
http://vistaswx.com/blog/article/category/tutorial/page/2 VB6 GDI+ 入门教程[4] 文字绘制 2009 年 6 月 18 日 7条评论 ...
- 常用的MYSQL 命令
例1:建立一个名为xhkdb的数据库 mysql> create database xhkdb; 例2:显示数据库 命令:show databases (注意:最后有个s)mysql> ...
- OA系统部门结构树
public class DepartmentUtils { /** * @param topList 顶级部门列表 * @param removeId 删除部门的id * @return */ pu ...
- Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio » 增、查、改、删操作
Create, Read, Update, and Delete operations¶ 5 of 5 people found this helpful By Tom Dykstra The Con ...
- 可持久化Trie & 可持久化平衡树 专题练习
[xsy1629]可持久化序列 - 可持久化平衡树 http://www.cnblogs.com/Sdchr/p/6258827.html [bzoj4260]REBXOR - Trie 事实上只是一 ...