MongoDB中的空间索引

为了支持对地理空间坐标数据的高效查询,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规范中定义的七种几何类型(PointMultiPointLineStringMultiLineStringPolygonMultiPolygonGeometryCollection)。

GeoJSON表示的这些几何类型与WKTWKB的很相似。

//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的类型还包括FeatureFeatureCollection两种。

Feature类型的GeoJSON对象必须包含一个geometry属性,且值为上述几何类型中的一种,及其它属性perprotiesFeatureCollection包含一个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提供以下空间查询操作:

nameDescription
$geoIntersects查询几何对象与指定的GeoJSON对象相交的文档。2dsphere 索引支持该操作。
$geoWithin查询几何对象在指定的GeoJSON对象边界内的文档。2dsphere2d索引都支持该操作。
$near返回几何对象在指定点附近的文档。2dsphere2d索引都支持该操作。
$nearSphere返回球体上某点附近的地理空间对象文档。2dsphere2d索引都支持该操作。
3.2 几何操作符
nameDescriptionformat
$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对象,2d2dsphere都支持{ <location field>: { $geoWithin: { $centerSphere: [ [<x>, <y> ], <radius> ] } } }
$geometry用于在空间查询操作中使用GeoJSON对象指定输入的几何对象。2d2dsphere都支持。$geometry: { type: “<GeoJSON object type>”, coordinates: [ <coordinates> ] }
$maxDistance用于过滤$near$nearSphere查询结果,指定最大距离。单位由坐标系决定(对于GeoJSON的点对象使用米为单位)。2d2dsphere都支持。db.places.find( { loc: { $near: [ -74 , 40 ], $maxDistance: 10 } } )
$minDistance用于过滤$near$nearSphere操作的查询结果,限定结果文档中的几何对象到中心点的最小距离。2d2dsphere索引都支持。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指定一个PolygonMultiPolygon类型的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/

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值