为了支持对地理空间坐标数据的高效查询,MongoDB提供了两个特殊的索引:2d索引(返回结果时使用平面几何)和2dsphere索引(返回结果时使用球面几何)。
# 1. MongoDB中的地理空间数据
在MongoDB中,用文档记录地球球体(地理坐标)上的位置信息,可以将数据存储为GeoJSON对象,如果用文档记录几何平面(投影坐标)上的位置信息,可以将数据存储为legacy coordinate pairs传统坐标对。
mongodb对地理坐标的GeoJSON对象进行的空间查询操作,使用的空间参考是WGS84。
# 1.1 GeoJSON (opens new window)对象
GeoJSON是一种基于JSON格式的地理空间数据交换格式。它定义了几种类型的JSON对象,通过这些JSON对象或其组合来表示地理空间数据的特征、性质和空间范围等信息。
GeoJSON默认使用的是地理坐标参考系统(WGS-84),单位是十进制的度。
一个GeoJSON对象可以是SFSQL (opens new window)规范中定义的七种几何类型(Point、MultiPoint、LineString、MultiLineString,Polygon,MultiPolygon,GeometryCollection)。
GeoJSON表示的这些几何类型与WKT和WKB的很相似。
//WKT:Point(102.0, 0.5)对应下面的geojson
{
"type": "Point",
"coordinates": [102.0, 0.5]
}
//WKT:LineString(102.0 0.0,103.0 1.0,104.0 0.0)
{
"type":"LineString",
"coordinates": [
[102.0, 0.0],
[103.0, 1.0],
[104.0, 0.0],
]
}
/*
* WKT:
Polygon(
(100.0 0.0,101.0 0.0,101.0 1.0,100.0 1.0,100.0 0.0)
)
*/
{
"type": "Polygon",
"coordinates": [
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0]
]
]
}
/**
*WKT:
MultiPoint((100.0 0.0),(101.0 1.0))
*/
{
"type": "MultiPoint",
"coordinates": [
[100.0, 0.0],
[101.0, 1.0]
]
}
/**
*WKT:
MultiLineString((100.0 0.0,101.0 1.0),(102.0 2.0,103.0 3.0))
*/
{
"type": "MultiLineString",
"coordinates": [
[
[100.0, 0.0],
[101.0, 1.0]
],
[
[102.0, 2.0],
[103.0, 3.0]
]
]
}
/**WTK:
*MultiPolygon(
((102.0 2.0,103.0 2.0,103.0 3.0,102.0 3.0,102.0 2.0)),
(
(100.0 0.0,101.0 0.0,100.1 1.0,100.0 1.0,100.0 0.0),
(100.2 0.2,100.2 0.8,100.8 0.8,100.8 0.2,100.2 0.2)
)
)
*/
{
"type": "MultiPolygon",
"coordinates": [
[
[
[102.0, 2.0],
[103.0, 2.0],
[103.0, 3.0],
[102.0, 3.0],
[102.0, 2.0]
]
],
[
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0]
],
[
[100.2, 0.2],
[100.2, 0.8],
[100.8, 0.8],
[100.8, 0.2],
[100.2, 0.2]
]
]
]
}
/**WKT:
*GeometryCollection(
POINT(100.0 0.0),
LINESTRING(101.0 0.0,102.0 1.0)
)
*/
{
"type": "GeometryCollection",
"geometries": [{
"type": "Point",
"coordinates": [100.0, 0.0]
}, {
"type": "LineString",
"coordinates": [
[101.0, 0.0],
[102.0, 1.0]
]
}]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
在SFSQL中,coordinate是一n个数字组成的数组,用来表示n维空间下点Point的位置信息。Geometry Object都有一个coordinates属性来表示几何体中的点位信息。
需要注意的是Polygon类型的GeoJSON对象,Polygon是由Linearing数组构成,第一个Linearing是面的外边界(Exterior boundary),其余的为面内的“洞”(Interior boundary),且不能相交或重叠,也不能共享边界。

LinearRing是一段封闭的分段的线状路径,(coordinates 的成员数组)至少4个坐标点,三个坐标可以确定LinearRing,第四个坐标用于闭合,与第一个坐标相同。一个
LinearRing必须遵循右手定则,外边界是逆时针的,而“洞”是顺时针方向。
另外,GeoJSON的类型还包括Feature和FeatureCollection两种。
Feature类型的GeoJSON对象必须包含一个geometry属性,且值为上述几何类型中的一种,及其它属性perproties。FeatureCollection包含一个Feature数组对象。
Feature
↙ ↘
Geometry properties
↙ ↓ ↘
Point Polyline Polygon
MultiPoint MultiLineString MultiPolygon
GeometryCollection
2
3
4
5
6
7
示例:
Feature类型的GeoJSON:
{
"type": "Feature",
"properties": {
"name": "测试点"
},
"geometry": {
"type": "Point",
"coordinates": [
113.95560264587402,
22.51267588902413
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
FeatureCollection类型的GeoJSON:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "测试线"
},
"geometry": {
"type": "LineString",
"coordinates": [
[
113.96212577819824,
22.515649230084094
],
[
113.96092414855956,
22.49241582330295
]
]
}
},
{
"type": "Feature",
"properties": {
"name": "测试点"
},
"geometry": {
"type": "Point",
"coordinates": [
113.95560264587402,
22.51267588902413
]
}
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
GeoJSON的类型是**【不可扩展】**的,只有固定的上面简述的九种。其中FeatureCollection是最常用的一种,像WFS服务,将响应格式设为application/json时,服务就会返回一个FeatureCollection类型的GeoJSON对象。
注意:"geometry type" 的值是大小写敏感的。
MongoDB中支持的GeoJSON对象类型只有上述简述的SFSQL (opens new window)规范中的七种geometry type。文档存储GeoJSON对象数据,通常是作为属性值嵌入到文档中,格式如下:
<field>:{
type:<GeoJSON type>,
coordinates:<coordinates>
}
2
3
4
- 必须有一个
type属性,且值为GeoJSON object type - 必须有一个
coordinates属性,用于表示几何对象的点位信息。
如果coordinates是经纬度的地理坐标,则其有效的经度值在-180到180之间,两者都包括在内,有效的纬度在-90到90之间。
# 1.2 Legacy Coordinate Pairs
对于平面坐标,建议是存储成Legacy Coordinate Pairs坐标对,可以使用2d索引。
存储坐标对数据可以使用数组或嵌入文档的形式:
//数组(优先考虑)
<field>: [<x>, <y>]
或
<field>: [<longitude>, <latitude>]
2
3
4
//嵌入文档
<field>: { <field1>: <x>, <field2>: <y> }
或
<field>: { <field1>: <longitude>, <field2>: <latitude> }
2
3
4
从上面的结构可以看出,坐标对的形式其实只适合存储Point类型的数据。
# 2. 地理空间索引
地理空间索引(Geospatial Index)
为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:使用平面几何数据(投影坐标)的二维索引(2d indexes)和使用球面几何(地理坐标)数据的二维球面索引(2dsphere indexes)。
# 2.1 2dsphere indexes
2dsphere索引支持在地球球体上的几何计算和支持所有MongoDB地理空间查询(包含,相交和接近等)。
2dsphere索引支持存储为GeoJSON对象和传统坐标对的数据。但对于传统坐标对,索引需要将数据转换为GeoJSON中的点类型。
创建2dsphere索引
创建一个2dsphere索引,可以使用db.collection.createIndex()方法,指定索引类型为2dsphere:
//单字段索引
db.collection.createIndex({<location field>: "2dsphere"})
2
其中的<location field>字段的值应为GeoJSON对象或legacy coordinates pair传统坐标对。如果在2dsphere索引字段中插入带有非几何数据的文档,或者在集合中的非几何数据的字段上构建2dsphere索引,则会操作失败(索引字段的限制)。
创建包含2dsphere索引的复合索引,可以包含多个位置信息的几何字段和非地理空间信息的字段:
//复合索引
db.sphere.createIndex({"location":"2dsphere","name":1})
2
# 2.2 2d indexes
2d indexes支持平面几何上的计算和查询,虽然该索引支持通过$nearSphere查询球面上的几何计算,但对于球面上的计算,还是尽可能的使用2dsphere索引。
创建2d索引
创建一个2d索引,可以使用db.collection.ceateIndex()方法,指定索引类型为2d:
db.collection.createIndex( { <location field> : "2d" } )
索引字段location field的值必须是legacy coordinates pair
# 3. 空间查询
MongoDB中的空间查询是基于空间索引基础之上的,所以进行空间查询前,因先创建地理空间索引。
# 3.1 查询操作
MongoDB提供以下空间查询操作:
| name | Description |
|---|---|
$geoIntersects | 查询几何对象与指定的GeoJSON对象相交的文档。2dsphere 索引支持该操作。 |
$geoWithin | 查询几何对象在指定的GeoJSON对象边界内的文档。2dsphere和2d索引都支持该操作。 |
$near | 返回几何对象在指定点附近的文档。2dsphere和2d索引都支持该操作。 |
$nearSphere | 返回球体上某点附近的地理空间对象文档。2dsphere和2d索引都支持该操作。 |
# 3.2 几何操作符
| name | Description | format |
|---|---|---|
$box | 在$geoWithin操作中使用传统坐标对(legacy coordinate pairs)指定矩形,只有2d index支持。 | { <location field>: { $geoWithin: { $box: [ [ <bottom left coordinates> ], [ <upper right coordinates> ] ] } } } |
$center | 在$geoWithin操作中使用传统坐标对(legacy coordinate pairs)指定圆形,只有2d index支持。 | { <location field>: { $geoWithin: { $center: [ [ <x>,<y> ] , <radius> ] } } } |
$centerSphere | 当使用球面几何的地理空间查询时,在$geoWithin操作中使用传统坐标对或GeoJSON对象,2d和2dsphere都支持 | { <location field>: { $geoWithin: { $centerSphere: [ [<x>, <y> ], <radius> ] } } } |
$geometry | 用于在空间查询操作中使用GeoJSON对象指定输入的几何对象。2d和2dsphere都支持。 | $geometry: { type: "<GeoJSON object type>", coordinates: [ <coordinates> ] } |
$maxDistance | 用于过滤$near和$nearSphere查询结果,指定最大距离。单位由坐标系决定(对于GeoJSON的点对象使用米为单位)。2d和2dsphere都支持。 | db.places.find( { loc: { $near: [ -74 , 40 ], $maxDistance: 10 } } ) |
$minDistance | 用于过滤$near和$nearSphere操作的查询结果,限定结果文档中的几何对象到中心点的最小距离。2d和2dsphere索引都支持。 | db.places.find( { location: { $nearSphere: { $geometry: { type : "Point", coordinates : [ -73.9667, 40.78 ] }, $minDistance: 1000, $maxDistance: 5000 }} } ) |
$polygon | 为$geoWithin查询指定一个使用传统坐标对的多边形。只有2d索引支持该操作。 | db.places.find( { loc: { $geoWithin: { $polygon: [ [ 0 , 0 ], [ 3 , 6 ], [ 6 , 0 ] ] } } } ) |
$uniqueDocs | 地理空间查询不返回重复的结果。从2.6开始就被废弃了,$uniqueDocs操作符对结果没有影响。 |
$geoIntersects操作使用$geometry指定GeoJSON对象
{
<location field>: {
$geoIntersects: {
$geometry: {
type: "<GeoJSON object type>" ,
coordinates: [ <coordinates> ]
}
}
}
}
2
3
4
5
6
7
8
9
10
11
$geoWithin操作也是使用$geometry指定一个Polygon或MultiPolygon类型的GeoJSON对象作为输入:
{
<location field>: {
$geoWithin: {
$geometry: {
type: <"Polygon" or "MultiPolygon"> ,
coordinates: [ <coordinates> ]
}
}
}
}
2
3
4
5
6
7
8
9
10
$near操作与$maxDistance和$minDistance操作符一起使用,返回以指定点为中心点,在限定距离范围内的文档。$near操作的输入可以是GeoJSON格式的数据也可以是坐标对的数据,对空间索引的要求有区别:
- 对
GeoJSON类型的点,需要使用2dsphere索引 - 对坐标对格式的点数据,需要使用
2d索引
//GeoJSON Point,unit is meters
{
<location field>: {
$near: {
$geometry: {
type: "Point" ,
coordinates: [ <longitude> , <latitude> ]
},
$maxDistance: <distance in meters>,
$minDistance: <distance in meters>
}
}
}
// legacy coordinates,unit is radians
{
$near: [ <x>, <y> ],
$maxDistance: <distance in radians>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$nearSphere操作是针对地理坐标进行计算的,返回指定球面上距离中心点在某段范围内的文档。当然,地理坐标您可以存储为GeoJSON的格式,也可以存储为传统坐标对的形式。
- 当文档中的几何数据格式是
GeoJSON时,建议使用GeoJSON类型的点作为输入,且使用2dsphere索引; - 当文档中的位置信息格式是传统坐标对时,使用传统坐标对作为输入,且使用
2d索引。其实$nearSphere操作也可以在GeoJSON格式的数据上使用2d索引。
//GeoJSON 格式输入,单位为米
{
$nearSphere: {
$geometry: {
type : "Point",
coordinates : [ <longitude>, <latitude> ]
},
$minDistance: <distance in meters>,
$maxDistance: <distance in meters>
}
}
//传统坐标对格式输入,单位为弧度
{
$nearSphere: [ <x>, <y> ],
$minDistance: <distance in radians>,
$maxDistance: <distance in radians>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.3 实例
# 数据准备

> use geodata
switched to db geodata
> db.sphere.insert(
{"name":"测试点","location":{"type": "Point","coordinates": [113.92024040222168,22.548708470991805]}})
> db.sphere.insert(
{"name":"线段1","location":{"type": "LineString","coordinates": [[113.92993927001953,22.535707699328004],[113.91483306884766,22.504310546471817]]}})
> db.sphere.insert(
{"name":"线段2","location":{"type": "LineString","coordinates": [[113.92204284667969,22.572487200676317],[113.99070739746094,22.518265717308317]]}})
> db.sphere.insert(
{"name": "线段3","location":{"type": "LineString","coordinates": [[113.97457122802734,22.562976200808055],[113.9725112915039,22.484644051870895]]}})
> db.sphere.insert(
{"name": "面1","location":{"type": "Polygon","coordinates": [[[113.89663696289062,22.581997544284242],[113.89869689941406,22.53507348402533],[113.90865325927733,22.491940013104305],[113.95397186279297,22.554098675696263],[113.89663696289062,22.581997544284242]]]}})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建2dsphere索引
> db.sphere.createIndex({"location":"2dsphere"})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.sphere.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"location" : "2dsphere"
},
"name" : "location_2dsphere",
"2dsphereIndexVersion" : 3
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 空间查询操作
使用线段2作为输入,执行geoIntersects操作:
> db.sphere.find({location:{
... $geoIntersects: {
... $geometry: {
... "type": "LineString",
... "coordinates": [
... [113.92204284667969,22.572487200676317],
... [113.99070739746094,22.518265717308317]
... ]
... }
... }
... }})
{ "_id" : ObjectId("600f9a8d264f9b09033f1fe7"), "name" : "线段2", "location" : { "type" : "LineString", "coordinates" : [ [ 113.92204284667969, 22.572487200676317 ], [ 113.99070739746094, 22.518265717308317 ] ] } }
{ "_id" : ObjectId("600f9a9f264f9b09033f1fe9"), "name" : "面1", "location" : { "type" : "Polygon", "coordinates" : [ [ [ 113.89663696289062, 22.581997544284242 ], [ 113.89869689941406, 22.53507348402533 ], [ 113.90865325927733, 22.491940013104305 ], [ 113.95397186279297, 22.554098675696263 ], [ 113.89663696289062, 22.581997544284242 ] ] ] } }
{ "_id" : ObjectId("600f9a98264f9b09033f1fe8"), "name" : "线段3", "location" : { "type" : "LineString", "coordinates" : [ [ 113.97457122802734, 22.562976200808055 ], [ 113.9725112915039, 22.484644051870895 ] ] } }
>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用面1作为输入,执行$geoWithin操作
> db.sphere.find({location:{$geoWithin: {$geometry: {"type": "Polygon","coordinates": [[[113.89663696289062,22.581997544284242],[113.89869689941406,22.53507348402533],[113.90865325927733,22.491940013104305],[113.95397186279297,22.554098675696263],[113.89663696289062,22.581997544284242]]]}}}})
{ "_id" : ObjectId("600f9a9f264f9b09033f1fe9"), "name" : "面1", "location" : { "type" : "Polygon", "coordinates" : [ [ [ 113.89663696289062, 22.581997544284242 ], [ 113.89869689941406, 22.53507348402533 ], [ 113.90865325927733, 22.491940013104305 ], [ 113.95397186279297, 22.554098675696263 ], [ 113.89663696289062, 22.581997544284242 ] ] ] } }
{ "_id" : ObjectId("600f9a0c264f9b09033f1fe6"), "name" : "线段1", "location" : { "type" : "LineString", "coordinates" : [ [ 113.92993927001953, 22.535707699328004 ], [ 113.91483306884766, 22.504310546471817 ] ] } }
{ "_id" : ObjectId("600f92c5264f9b09033f1fe5"), "name" : "测试点", "location" : { "type" : "Point", "coordinates" : [ 113.92024040222168, 22.548708470991805 ] } }
2
3
4
5
使用测试点作为输入,执行$nearSphere操作。结果会按距离中心点距离,由远到近进行排序。
db.sphere.find({
location:{
$nearSphere:{
$geometry:{
type:"Point",
coordinates:[113.92024040222168,22.548708470991805]
},
$minDistance: 100,
$maxDistance: 10000
}
}
})
{ "_id" : ObjectId("600f9a0c264f9b09033f1fe6"), "name" : "线段1", "location" : { "type" : "LineString", "coordinates" : [ [ 113.92993927001953, 22.535707699328004 ], [ 113.91483306884766, 22.504310546471817 ] ] } }
{ "_id" : ObjectId("600f9a8d264f9b09033f1fe7"), "name" : "线段2", "location" : { "type" : "LineString", "coordinates" : [ [ 113.92204284667969, 22.572487200676317 ], [ 113.99070739746094, 22.518265717308317 ] ] } }
{ "_id" : ObjectId("600f9a98264f9b09033f1fe8"), "name" : "线段3", "location" : { "type" : "LineString", "coordinates" : [ [ 113.97457122802734, 22.562976200808055 ], [ 113.9725112915039, 22.484644051870895 ] ] } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 参考文章
[1] 2d Index Internals https://docs.mongodb.com/manual/core/geospatial-indexes/
[2] The GeoJSON Format https://tools.ietf.org/html/rfc7946#section-3.1
[3] GeoJSON Objects https://docs.mongodb.com/manual/reference/geojson/
[4] Simple Feature Access - Part 2: SQL Option https://www.ogc.org/standards/sfs
[5] Geospatial Query Operators https://docs.mongodb.com/manual/reference/operator/query-geospatial/