MongoDB系列五(地理空間索引與查詢).


 一、經緯度表示方式

    MongoDB 中對經緯度的存儲有着自己的一套規范(主要是為了可以在該字段上建立地理空間索引)。包括兩種方式,分別是 Legacy Coordinate Pairs (這個詞實在不知道怎么翻譯...) 和  GeoJSON 。

  • Legacy Coordinate Pairs

Legacy Coordinate Pairs 又有兩種方式可以存儲經緯度,可以使用數組(首選)或嵌入式文檔。

數組:

<field>: [<longitude>, <latitude> ]

嵌入式文檔:

<field>: { <field1>: <longitude>, <field2>: <latitude> }

tips:有效經度值介於-180和180之間。有效緯度值介於-90和90之間。

  • GeoJSON

GeoJson 比 Legacy Coordinate Pairs 要強大的多,Legacy Coordinate Pairs 僅僅用來保存一個經緯度,而 GeoJson 可以用來指定點、線和多邊形。

點可以用形如[longitude, latitude]([經度,緯度])的兩個元素的數組表示:

{
"geometry": {
        "type": "Point",
        "coordinates": [125.6, 10.1]
   }
}

線可以用一個由點組成的數組來表示:

{
"geometry": {
        "type": "LineString",
         "coordinates": [[125.6, 10.1],[125.6,10.2],[125.6,10.3]]
   }
}

多邊形的表示方式與線一樣(都是一個由點組成的數組),但是"type"不同:

{
"geometry": {
        "type": "Polygon",
         "coordinates": [[125.6, 10.1],[125.5,10.2],[125.7,10.3]]
  }
}

type 除了 Point(點)、LineString(線)、Polygon(多邊形),還有 MultiPoint(多點)、MultiLineString(多個線) 和  MultiPolygon(多個多邊形)。

"geometry"字段的名字可以是任意的,但是其中的子對象是由GeoJSON指定的,不能改變。

二、地理空間索引

  • 2dsphere索引

2dsphere索引用於地球表面類型的地圖,允許使用在 Legacy Coordinate Pairs 保存的經緯度字段上和使用GeoJSON格式保存的點、線和多邊形字段上。

  db.world.ensureIndex({"geometry" : "2dsphere"})
  • 2d索引

 對於非球面地圖(游戲地圖、時間連續的數據等),可以使用"2d"索引代替"2dsphere"。

 2d 索引 僅允許使用在 Legacy Coordinate Pairs 保存的經緯度字段上。

  db.world.ensureIndex({"geometry" : "2d"})
  • 區別:

2dsphere索引只支持球形查詢(即球面上幾何圖形的查詢)。

2d索引支持平面查詢(即在平面上幾何圖形的查詢)和一些球形查詢。雖然2d索引支持一些球形查詢,但是對這些球形查詢使用2d索引可能會導致錯誤,例如極點附近會出現大量的扭曲變形。
2d索引只能對點進行索引。可以保存一個由點組成的數組,但是它只會被保存為由點組成的數組,不會被當成線。特別是對於"$geoWithin"查詢來說,這是一項重要的區別。如果將街道保存為由點組成的數組,那么如果其中的某個點位於給定的形狀之內,這個文檔就會與$geoWithin相匹配。但是,由這些點組成的線並不一定完全包含在這個形狀之內。

三、地理空間查詢

    可以使用多種不同類型的地理空間查詢:交集(intersection)、包含(within)以及接近(nearness)。

  • $geoIntersects

定義:指出與查詢位置相交的文檔。

支持的索引:2dsphere

幾何操作符:

    1. $geometry (僅支持 2dsphere 索引,指定GeoJSON格式的幾何圖形)
  • $geoWithin

定義:指出完全包含在某個區域的文檔。

支持的索引:2dsphere、2d

幾何操作符:

  1. $box(僅支持 2d 索引,查詢出矩形范圍內的所有文檔)
  2. $center(僅支持 2d 索引,查詢出圓形范圍內的所有文檔)
  3. $polygon (僅支持 2d 索引,查詢出多邊形范圍內的所有文檔)
  4. $centerSphere(支持 2d 索引和 2dsphere 索引,查詢出球面圓形范圍內的所有文檔)
  5. $geometry (僅支持 2dsphere 索引,指定GeoJSON格式的幾何圖形)
  • $near

定義:指出與查詢位置從最近到最遠的文檔。

支持的索引:2dsphere、2d

幾何操作符:

  1. $maxDistance (支持 2dsphere 索引和 2d 索引,指定查詢結果的最大距離)
  2. $minDistance (僅支持 2dsphere 索引,指定查詢結果的最小距離。在4.0后支持 2d 索引)
  3. $geometry (僅支持 2dsphere 索引,指定GeoJSON格式的點)

備注:$minDistance 官方文檔說僅支持 2dsphere 索引,但是我實踐證明 $minDistance 也支持 2d 索引,大家可以試試看,這里保留爭議。

  • $nearSphere

定義:使用球面幾何計算近球面的距離,指出與查詢位置從最近到最遠的文檔。

支持的索引:2dsphere、2d

幾何操作符:

  1. $maxDistance (支持 2dsphere 索引和 2d 索引,指定查詢結果的最大距離)
  2. $minDistance (僅支持 2dsphere 索引,指定查詢結果的最小距離。在4.0后支持 2d 索引)
  3. $geometry (僅支持 2dsphere 索引,指定GeoJSON格式的點)

備注:MongoDB 4.0 后對地理空間索引增加的支持?

1、地理空間查詢操作符 $near 和 $nearSphere 支持查詢的分片集合。

2、MongoDB 4.0 為 $geoNear 聚合運算符和 geoNear 命令添加了一個選項 key,使用戶可以在查詢時指定要使用的地理空間索引。以前,要使用 $geoNear 聚合運算符或 geoNear 命令,集合只能有一個地理空間索引。

 

db.places.aggregate([
   {
     $geoNear: {
        near: { type: "Point", coordinates: [ -73.98142 , 40.71782 ] },
        key: "location",
        distanceField: "dist.calculated",
        query: { "category": "Parks" }
     }
   }
])

 

四、實踐

  • "$geoIntersects" 操作符找出與查詢位置相交的文檔 ?
db.driverPoint.find(
   {
     coordinate: {
       $geoIntersects: {
          $geometry: {
             type: "Polygon" ,
             coordinates: [
               [ [ 118.193828, 24.492242 ], [ 118.193953, 24.702114 ], [ 118.19387, 24.592242 ],[ 118.193828, 24.492242 ]]
             ]
          }
       }
     }
   }
)
View Code

 tips:coordinates 表示多邊形,第一個點 和 最后一個點 必須相同,因為這樣才能拼成一個多邊形呀!

  • "$geoWithin"操作符找出完全包含在某個區域的文檔?
db.driverPoint.find(
   {
     coordinate: {
       $geoWithin: {
          $geometry: {
             type: "Polygon" ,
             coordinates: [
               [ [ 118.193828, 24.492242 ], [ 118.193953, 24.702114 ], [ 119.19387, 28.792242 ],[ 118.193828, 24.492242 ]]
             ]
          }
       }
     }
   }
)
View Code
  • "$geoWithin"操作符找出矩形范圍內的文檔?
db.driverPoint.find(
{
  coordinate: {
     $geoWithin: {
        $box: [
          [ 118.0,24.0 ],
          [ 120.0,30.0 ]
        ]
     }
  }
}
)
View Code

 tips:"$box"接受一個兩元素的數組:第一個元素指定左下角的坐標,第二個元素指定右上角的坐標。

  • "$geoWithin"操作符找出圓形范圍內的文檔?
db.driverPoint.find(
{
  coordinate: {
     $geoWithin: {
         $center: [ [ 118.067678, 24.444373] , 10 ] 
     }
  }
}
)
View Code

tips:"$center"接受一個兩元素數組作為參數:第一個元素是一個點,用於指定圓心;第二個參數用於指定半徑。

  • "$geoWithin"操作符找出多邊形范圍內的文檔?
db.driverPoint.find(
{
  coordinate: {
     $geoWithin: {
         $polygon: [ [ 118.067678 , 24.444373 ], [ 119.067678 , 25.444373 ], [ 120.067678 , 26.444373 ] ]
     }
  }
}
)
View Code

tips"$polygon" 列表中的最后一個點會被連接到第一個點,以便組成多邊形。

  • "$geoWithin"操作符找出球面圓形范圍內的文檔?
db.driverPoint.find(
{
  coordinate: {
     $geoWithin: {
         $centerSphere: [ [ 118.067678, 24.444373 ], 10/3963.2 ]
     }
  }
}
)
View Code

tips:該例子表示 距離 [118.067678, 24.444373] 中心點10 英里范圍內的所有文檔,查詢通過除以地球的大約赤道半徑(3963.2英里)將距離轉換為弧度。

  • $near 找出距離一個點相應距離內的文檔?

geoJson 格式(僅支持 2dsphere 索引):

db.driverPoint.find(
{
   coordinate: {
     $near: {
       $geometry: {
          type: "Point" ,
          coordinates: [ 118.067678 , 24.444373 ]
       },
       $maxDistance: 3000,
       $minDistance: 0
     }
   }
}
)
View Code

Legacy Coordinate Pairs 格式(僅支持 2d 索引):

db.driverPoint.find(
{
   coordinate: {
     $near: [ 118.193828 , 24.492242 ],
     $maxDistance: 0.10
     }
}
)
View Code

tips:1、$near 當用 geoJson 格式表示時, 距離單位是米(meter)。

         2、$near 當用 Legacy Coordinate Pairs 格式表示時,距離單位是弧度(radian)。  

         3、"$near"是唯一一個會對查詢結果進行自動排序的地理空間操作符:"$near"的返回結果是按照距離由近及遠排序的。

五、結語

    怎么說呢?學習這方面的知識老是給我一種特別亂的感覺。稍微總結下吧!MongoDB 對於地理空間的查詢 是基於 它對 地理空間的索引(即2dsphere 和 2d)來實現的。所以,我們只要搞清楚什么時候 該建立 2dsphere 索引,什么時候該建立 2d 索引,然后再找適用於該索引的操作符就很清晰明了了!總之,geoJSON 格式保存的經緯度一定 建立 2dsphere 索引。Legacy Coordinate Pairs 格式保存的經緯度 僅在表示 平面地圖的時候才考慮建立 2d 索引,其他情況還是選擇 2dsphere 索引。

    Spring Data MongoDB 中對地理位置的查詢可參考 https://github.com/JMCuixy/SpringDataMongoDB 中單元測試的 Test03.java。

 

 

參考資料:

1、《MongoDB 權威指南第二版》

2、https://docs.mongodb.com/manual/reference/operator/query-geospatial/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM