地理位置索引支持是MongoDB的一大亮点,这也是全球最流行的LBS服务foursquare 选择MongoDB的原因之一。我们知道,通常的数据库索引结构是B+ Tree,如何将地理位置转化为可建立B+Tree的形式,下文将为你描述。

首先假设我们将需要索引的整个地图分成16×16的方格,如下图(左下角为坐标0,0 右上角为坐标16,16):

如果我们使用sql,是不是要把整个中国地图,进行划分呢?如果有区域的概念,那应该不一个地区的地图经纬度选择出来,然后划分。

 

比如可以先按照距离基本来过滤,然后在一个大的矩形内进行 距离的计算,最后选择最近的N的点  建立一个二维空间索引

 

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,不过测试可以建多个,如下