20 KiB
title | date | author | tags | categories | |||
---|---|---|---|---|---|---|---|
MongoDB中的空间索引 | 2021-01-21 | ac |
|
|
为了支持对地理空间坐标数据的高效查询,MongoDB提供了两个特殊的索引:2d索引(返回结果时使用平面几何)和2dsphere索引(返回结果时使用球面几何)。
1. MongoDB
中的地理空间数据
在MongoDB
中,用文档记录地球球体(地理坐标)上的位置信息,可以将数据存储为GeoJSON
对象,如果用文档记录几何平面(投影坐标)上的位置信息,可以将数据存储为legacy coordinate pairs
传统坐标对。
mongodb
对地理坐标的GeoJSON
对象进行的空间查询操作,使用的空间参考是WGS84
。
1.1 GeoJSON对象
GeoJSON
是一种基于JSON
格式的地理空间数据交换格式。它定义了几种类型的JSON
对象,通过这些JSON
对象或其组合来表示地理空间数据的特征、性质和空间范围等信息。
GeoJSON
默认使用的是地理坐标参考系统(WGS-84
),单位是十进制的度。
一个GeoJSON
对象可以是SFSQL规范中定义的七种几何类型(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]
]
}]
}
在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
示例:
Feature
类型的GeoJSON
:
{
"type": "Feature",
"properties": {
"name": "测试点"
},
"geometry": {
"type": "Point",
"coordinates": [
113.95560264587402,
22.51267588902413
]
}
}
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
]
}
}
]
GeoJSON
的类型是**【不可扩展】**的,只有固定的上面简述的九种。其中FeatureCollection
是最常用的一种,像WFS
服务,将响应格式设为application/json
时,服务就会返回一个FeatureCollection
类型的GeoJSON
对象。
注意:"geometry type" 的值是大小写敏感的。
MongoDB
中支持的GeoJSON
对象类型只有上述简述的SFSQL规范中的七种geometry type
。文档存储GeoJSON
对象数据,通常是作为属性值嵌入到文档中,格式如下:
<field>:{
type:<GeoJSON type>,
coordinates:<coordinates>
}
- 必须有一个
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>]
//嵌入文档
<field>: { <field1>: <x>, <field2>: <y> }
或
<field>: { <field1>: <longitude>, <field2>: <latitude> }
从上面的结构可以看出,坐标对的形式其实只适合存储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"})
其中的<location field>
字段的值应为GeoJSON
对象或legacy coordinates pair
传统坐标对。如果在2dsphere
索引字段中插入带有非几何数据的文档,或者在集合中的非几何数据的字段上构建2dsphere
索引,则会操作失败(索引字段的限制)。
创建包含2dsphere
索引的复合索引,可以包含多个位置信息的几何字段和非地理空间信息的字段:
//复合索引
db.sphere.createIndex({"location":"2dsphere","name":1})
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> ]
}
}
}
}
$geoWithin
操作也是使用$geometry
指定一个Polygon
或MultiPolygon
类型的GeoJSON
对象作为输入:
{
<location field>: {
$geoWithin: {
$geometry: {
type: <"Polygon" or "MultiPolygon"> ,
coordinates: [ <coordinates> ]
}
}
}
}
$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>
}
$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>
}
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]]]}})
创建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作为输入,执行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 ] ] } }
>
使用面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 ] } }
使用测试点作为输入,执行$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 ] ] } }
参考文章
[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/