$geoNear
怎么使用mongoose的geoNear??
2014-11-26 15:05:20|??分類:?mongodb?|??標簽:mongoose??|舉報|字號?訂閱
???? 下載LOFTER我的照片書??|起因
在開發的時候碰到一個情況,數據源有100個景點,需要對給出的一個點,求距離它最近的景點,并且在前端下拉翻頁的過程中,依距離順序依次顯示出來。大眾點評做出了這個效果,但是以我現階段的經驗,想不出比較完美的解決方案。尤其是處理下拉翻頁還能保證排序的效果,這一點是需要先對所有的點求距離,然后將所有點進行排序然后輸出嗎?這樣效率不低嗎?后來想到,mongodb有一個查詢操作是geoNear,于是思考通過這種方案來解決排序效率的問題。
過程
在網上查了一下,大概需要三步:
使用geoNear
Model.geoNear([1,3], { maxDistance : 5, spherical : true }, function(err,results, stats) {console.log(results); });或者確保有
確保有GeoJSON數據結構
{ <location field>: { type: "<GeoJSON type>" , coordinates: <coordinates> } }查找
// geoJsonvar point = { type : "Point", coordinates : [9,9] };Model.geoNear(point, { maxDistance : 5, spherical : true }, function(err, results, stats) {console.log(results);});擴展
利用aggregate
models.Attraction.aggregate([{"$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} );db.runCommand(
{
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
轉載于:https://www.cnblogs.com/jayruan/p/5423668.html
總結