日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

结合MongoDB开发LBS应用

發布時間:2025/7/25 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 结合MongoDB开发LBS应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

http://www.cnblogs.com/jifeng/p/4356052.html

然后列舉一下需求:
1.實時性要高,有頻繁的更新和讀取
2.可按距離排序支持分頁
3.支持多條件篩選(一個經緯度數據還包含其他屬性,比如社交系統的性別、年齡)

方案簡單介紹:
1.sphinx geo索引
支持按照距離排序,并支持分頁。但是嘗試mva+geo失敗,還在找原因。
無法滿足高實時性需求。(可能是不了解實時增量索引配置有誤)
資源占用小,速度快

2.mongodb geo索引
支持按照距離排序,并支持分頁。支持多條件篩選。
可滿足實時性需求。
資源占用大,數據量達到百萬級請流量在10w左右查詢速度明顯下降。

3.mysql+geohash/ mysql sql查詢
不支持按照距離排序(代價太大)。支持分頁。支持多條件篩選。
可滿足實時性需求。
資源占用中等,查詢速度不及mongodb。
且geohash按照區塊將球面轉化平面并切割。暫時沒有找到跨區塊查詢方法(不太了解)。

4.redis+geohash
geohash缺點不再贅述
不支持距離排序。支持分頁查詢。不支持多條件篩選。
可滿足實時性需求。
資源占用最小。查詢速度很快。

?

?

http://blog.csdn.net/huangrunqing/article/details/9112227

簡介

隨著近幾年各類移動終端的迅速普及,基于地理位置的服務(LBS)和相關應用也越來越多,而支撐這些應用的最基礎技術之一,就是基于地理位置信息的處理。我所在的項目也正從事相關系統的開發,我們使用的是Symfony2+Doctrine2 ODM+MongoDB的組合。

我們將這些技術要點整理成文,希望能夠通過本文的介紹和案例,詳細解釋如何使用MongoDB進行地理位置信息的查詢和處理。在文章的開頭,我們也會先介紹一下業界通常用來處理地理位置信息的一些方案并進行比較,讓讀者逐步了解使用MongoDB查詢及處理地理位置信息的優勢。

本文使用了Symfony2和Doctrine2作為Web應用的開發框架,對于想了解Symfony2的數據庫操作的讀者來說閱讀本文也可以了解和掌握相關的技術和使用方法。

1. LBS類應用特點

不管是什么LBS應用,一個共同的特點就是:他們的數據都或多或少包含了地理位置信息。而如何對這些信息進行查詢、處理、分析,也就成為了支撐LBS應用的最基礎也是最關鍵的技術問題。

而由于地理位置信息的特殊性,在開發中經常會有比較難以處理的問題出現,比如:由于用戶所在位置的不固定性,用戶可能會在很小范圍內移動,而此時經緯度值也會隨之變化;甚至在同一個位置,通過GPS設備獲取到的位置信息也可能不一樣。所以如果通過經緯度去獲取周邊信息時,就很難像傳統數據庫那樣做查詢并進行緩存。

對于這個問題,有讀者可能會說有別的處理方案,沒錯,比如只按經緯度固定的幾位小數點做索引,比如按矩陣將用戶劃分到某固定小范圍的區域(可以參考后文將會提到的geohash)等方式,雖然可以繞個彎子解決,但或多或少操作起來比較麻煩,也會犧牲一些精度,甚至無法做到性能的最優化,所以不能算作是最佳的解決辦法。

而最近幾年,直接支持地理位置操作的數據庫層出不窮,其操作友好、性能高的特性也開始被我們慢慢重視起來,其中的佼佼者當屬MongoDB。

MongoDB在地理位置信息的處理上有什么優勢?下面我們通過一個簡單的案例來對比一下各種技術方案之間進行進行地理位置信息處理的差異。

2. 幾個地理位置信息處理方案的對比和分析

1. 確定功能需求

對于任何LBS應用來說,讓用戶尋找周圍的好友可能都是一個必不可少的功能,我們就以這個功能為例,來看看各種處理方案之間的差異和區別。

我們假設有如下功能需求:

  • 顯示我附近的人
  • 由近到遠排序
  • 顯示距離

2. 可能的技術方案

排除一些不通用和難以實現的技術,我們羅列出以下幾種方案:

  • 基于MySQL數據庫
  • 采用GeoHash索引,基于MySQL
  • MySQL空間存儲(MySQL Spatial Extensions)
  • 使用MongoDB存儲地理位置信息
  • 我們一個個來分析這幾種方案。

    方案1:基于MySQL數據庫

    MySQL的使用非常簡單。對于大部分已經使用MySQL的網站來說,使用這種方案沒有任何遷移和部署成本。

    而在MySQL中查詢“最近的人”也僅需一條SQL即可,

    SELECT id, ( 6371 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians ( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distanceFROM places HAVING distance < 25 ORDER BY distance LIMIT 0 , 100;

    注:這條SQL查詢的是在lat,lng這個坐標附近的目標,并且按距離正序排列,SQL中的distance單位為公里。

    但使用SQL語句進行查詢的缺點也顯而易見,每條SQL的計算量都會非常大,性能將會是嚴重的問題。

    先別放棄,我們嘗試對這條SQL做一些優化。

    可以將圓形區域抽象為正方形,如下圖

    根據維基百科上的球面計算公式,可以根據圓心坐標計算出正方形四個點的坐標。

    然后,查詢這個正方形內的目標點。

    SQL語句可以簡化如下:

    SELECT * FROM places WHERE ((lat BETWEEN ? AND ?) AND (lng BETWEEN ? AND ?))

    這樣優化后,雖然數據不完全精確,但性能提升很明顯,并且可以通過給lat lng字段做索引的方式進一步加快這條SQL的查詢速度。對精度有要求的應用也可以在這個結果上再進行計算,排除那些在方塊范圍內但不在原型范圍內的數據,已達到對精度的要求。

    可是這樣查詢出來的結果,是沒有排序的,除非再進行一些SQL計算。但那又會在查詢的過程中產生臨時表排序,可能會造成性能問題。

    方案2:GeoHash索引,基于MySQL

    GeoHash是一種地址編碼,通過切分地圖區域為小方塊(切分次數越多,精度越高),它能把二維的經緯度編碼成一維的字符串。也就是說,理論上geohash字符串表示的并不是一個點,而是一個矩形區域,只要矩形區域足夠小,達到所需精度即可。(其實MongoDB的索引也是基于geohash)

    如:wtw3ued9m就是目前我所在的位置,降低一些精度,就會是wtw3ued,再降低一些精度,就會是wtw3u。(點擊鏈接查看坐標編碼對應Google地圖的位置)

    所以這樣一來,我們就可以在MySQL中用LIKE ‘wtw3u%’來限定區域范圍查詢目標點,并且可以對結果集做緩存。更不會因為微小的經緯度變化而無法用上數據庫的Query Cache。

    這種方案的優點顯而易見,僅用一個字符串保存經緯度信息,并且精度由字符串從頭到尾的長度決定,可以方便索引。

    但這種方案的缺點是:從geohash的編碼算法中可以看出,靠近每個方塊邊界兩側的點雖然十分接近,但所屬的編碼會完全不同。實際應用中,雖然可以通過去搜索環繞當前方塊周圍的8個方塊來解決該問題,但一下子將原來只需要1次SQL查詢變成了需要查詢9次,這樣不僅增大了查詢量,也將原本簡單的方案復雜化了。

    除此之外,這個方案也無法直接得到距離,需要程序協助進行后續的排序計算。

    方案3:MySQL空間存儲

    MySQL的空間擴展(MySQL Spatial Extensions),它允許在MySQL中直接處理、保存和分析地理位置相關的信息,看起來這是使用MySQL處理地理位置信息的“官方解決方案”。但恰恰很可惜的是:它卻不支持某些最基本的地理位置操作,比如查詢在半徑范圍內的所有數據。它甚至連兩坐標點之間的距離計算方法都沒有(MySQL Spatial的distance方法在5.*版本中不支持)

    官方指南的做法是這樣的:

    GLength(LineStringFromWKB(LineString(point1, point2)))

    這條語句的處理邏輯是先通過兩個點產生一個LineString的類型的數據,然后調用GLength得到這個LineString的實際長度。

    這么做雖然有些復雜,貌似也解決了距離計算的問題,但讀者需要注意的是:這種方法計算的是歐式空間的距離,簡單來說,它給出的結果是兩個點在三維空間中的直線距離,不是飛機在地球上飛的那條軌跡,而是筆直穿過地球的那條直線。

    所以如果你的地理位置信息是用經緯度進行存儲的,你就無法簡單的直接使用這種方式進行距離計算。

    方案4:使用MongoDB存儲地理位置信息

    MongoDB原生支持地理位置索引,可以直接用于位置距離計算和查詢。

    另外,它也是如今最流行的NoSQL數據庫之一,除了能夠很好地支持地理位置計算之外,還擁有諸如面向集合存儲、模式自由、高性能、支持復雜查詢、支持完全索引等等特性。

    對于我們的需求,在MongoDB只需一個命令即可得到所需要的結果:

    db.runCommand( { geoNear: "places", near: [ 121.4905, 31.2646 ], num:100 })

    查詢結果默認將會由近到遠排序,而且查詢結果也包含目標點對象、距離目標點的距離等信息。

    由于geoNear是MongoDB原生支持的查詢函數,所以性能上也做到了高度的優化,完全可以應付生產環境的壓力。

    方案總結

    基于MongoDB做附近查詢是很方便的一件事情。

    MongoDB在地理位置信息方面的表現遠遠不限于此,它還支持更多更加方便的功能,如范圍查詢、距離自動計算等。

    接下來,我們結合Symfony2來詳細地演示一些使用MongoDB進行地理位置信息處理的例子。

    3. 結合Symfony2演示

    運行環境

    參考環境:Nginx1.2 + PHP5.4 + MongoDB2.4.3 + Symfony2.1

    建立coordinate和places兩個document文件,前者是作為places內的一個embed字段. 為方便演示效果,這里同時設置了兩個索引 2d 和 2dsphere

    Document/Coordinate.php/*** @MongoDB\EmbeddedDocument*/class Coordinate {/*** @MongoDB\Field(type="float")*/public $longitude;/*** @MongoDB\Field(type="float")*/public $latitude;...}Document/Place.php/*** @MongoDB\Document(collection="places")* @MongoDB\ChangeTrackingPolicy("DEFERRED_EXPLICIT")* @MongoDB\Indexes({* @MongoDB\Index(keys={"coordinate"="2d"}),* @MongoDB\Index(keys={"coordinate"="2dsphere"})* })*/class Place{/**** @MongoDB\Id(strategy="INCREMENT")*/protected $id;/*** @MongoDB\Field(type="string")*/protected $title;/*** @MongoDB\Field(type="string")*/protected $address;/*** @MongoDB\EmbedOne(targetDocument="HenterGEO\GEOBundle\Document\Coordinate")*/protected $coordinate;/*** @MongoDB\Distance*/public $distance;...}

    坐標保存以longitude, latitude這個順序(沒有明確的限制和區別,但我們在此遵循官方的推薦)。

    另外,為直觀顯示查詢效果,默認使用百度地圖標記查詢數據。

    程序說明

    我們用到的代碼包是doctrine/mongodb-odm-bundle(下文稱ODM),這個代碼包提供了在Symfony2環境下的MongoDB數據庫支持,使用這個代碼包,可以讓我們更加方便的在Symfony2環境下操作MongoDB數據庫。。

    ODM封裝了MongoDB中常用的一些地理位置函數,如周邊搜索和范圍搜索。

    ODM中的操作默認距離單位是度,只有geoSphere支持弧度單位(必須在參數中指定spherical(true))

    4. MongoDB的地理位置查詢

    注意事項

  • 下文大多數直接對MongoDB的數據庫操作將使用Mongo Shell進行演示。在演示網站頁面和功能時,將結合Symfony2、Doctrine-MongoDB進行演示。
  • 本文演示所用的MongoDB版本為2.4.3,版本號比較新,所以某些查詢方式在低版本里面并不支持。
  • 以places這個collection為例,大部分例子都需要類似下面格式的測試數據支持: { "_id" : 2, "coordinate" : { "longitude" : 121.3449, "latitude" : 31.17528 }, "title" : "僅售75元,市場價210元的頂呱呱田雞火鍋3-4人套餐,無餐具費,冬日暖鍋,歡迎品嘗", "address" : "閔行區航新路634號" }
  • 地理位置索引:

    MongoDB地理位置索引常用的有兩種。

    • 2d 平面坐標索引,適用于基于平面的坐標計算。也支持球面距離計算,不過官方推薦使用2dsphere索引。
    • 2dsphere 幾何球體索引,適用于球面幾何運算

    關于兩個坐標之間的距離,官方推薦2dsphere:

    MongoDB supports rudimentary spherical queries on flat 2d indexes for legacy reasons. In general, spherical calculations should use a 2dsphere index, as described in 2dsphere Indexes.

    不過,只要坐標跨度不太大(比如幾百幾千公里),這兩個索引計算出的距離相差幾乎可以忽略不計。

    建立索引:

    > db.places.ensureIndex({'coordinate':'2d'}) > db.places.ensureIndex({'coordinate':'2dsphere'})

    查詢方式:

    查詢方式分三種情況:

  • Inclusion。范圍查詢,如百度地圖“視野內搜索”。
  • Inetersection。交集查詢。不常用。
  • Proximity。周邊查詢,如“附近500內的餐廳”。
  • 而查詢坐標參數則分兩種:

  • 坐標對(經緯度)根據查詢命令的不同,$maxDistance距離單位可能是 弧度 和 平面單位(經緯度的“度”):

    db.<collection>.find( { <location field> :{ $nearSphere: [ <x> , <y> ] ,$maxDistance: <distance in radians>} } )
  • GeoJson $maxDistance距離單位默認為米:

    db.<collection>.find( { <location field> :{ $nearSphere :{ $geometry :{ type : "Point" ,coordinates : [ <longitude> , <latitude> ] } ,$maxDistance : <distance in meters>} } } )
  • 案例A:附近的人

    查詢當前坐標附近的目標,由近到遠排列。

    可以通過$near或$nearSphere,這兩個方法類似,但默認情況下所用到的索引和距離單位不同。

    查詢方式:

    > db.places.find({'coordinate':{$near: [121.4905, 31.2646]}}) > db.places.find({'coordinate':{$nearSphere: [121.4905, 31.2646]}})

    查詢結果:

    { "_id" : 115, "coordinate" : { "longitude" : 121.4915, "latitude" : 31.25933 }, "title" : "僅售148元,市場價298元的星程上服假日酒店全日房一間入住一天, 節假日通用,精致生活,品質享受", "address" : "虹口區天水路90號" }…(100條)

    上述查詢坐標[121.4905, 31.2646]附近的100個點,從最近到最遠排序。

    默認返回100條數據,也可以用limit()指定結果數量,如

    > db.places.find({'coordinate':{$near: [121.4905, 31.2646]}}).limit(2)

    指定最大距離 $maxDistance

    > db.places.find({'coordinate':{$near: [121.4905, 31.2646], $maxDistance:2}})

    結合Symfony2進行演示:

    這里用near,默認以度為單位,公里數除以111(關于該距離單位后文有詳細解釋)。

    /*** @Route("/near", name="near")* @Template()*/ public function nearAction(){$longitude = (float)$this->getRequest()->get('lon',121.4905);$latitude = (float)$this->getRequest()->get('lat',31.2646);//2km$max = (float)$this->getRequest()->get('max', 2);$places = $this->getPlaceRepository()->createQueryBuilder()->field('coordinate')->near($longitude, $latitude)->maxDistance($max/111)->getQuery()->toarray();return compact('places','max','longitude','latitude'); }

    通過 domain.dev/near 訪問,效果如下:

    longitude: xxx, latitude: xxx為當前位置,我們在地圖上顯示了周邊100條目標記錄

    案例B:區域內搜索

    MongoDB中的范圍搜索(Inclusion)主要用$geoWithin這個命令,它又細分為3種不同類型,如下:

  • $box 矩形
  • $center 圓(平面),$centerSphere圓(球面)
  • $polygon 多邊形
  • $center和$centerSphere在小范圍內的應用幾乎沒差別(除非這個圓半徑幾百上千公里)。

    下面我們介紹一下這三種查詢的案例。

    矩形區域

    這個比較常用,比如百度地圖的視野內搜索(矩形)、或搜狗地圖的“拉框搜索”

    定義一個矩形范圍,我們需要指定兩個坐標,在MongoDB的查詢方式如下:

    > db.places.find( { coordinate : { $geoWithin : { $box :[ [ 121.44, 31.25 ] , [ 121.5005, 31.2846 ] ] } } } )

    查詢結果:

    { "_id" : 90472, "title" : "【魯迅公園】僅售99元!酒店門市價288元的上海虹元商務賓館客房一間入住一天(需持本人有效 身份證件辦理登記):大床房/標準房(2選1)!不含早餐!不涉外!2012年9月29日-10月6日 不可使用拉手券!可延遲退房至14:00!", "address" : "上海市虹口區柳營路8號", "coordinate" : { "longitude" : 121.47, "latitude" : 31.27145 } } ... ...

    Symfony2演示代碼:

    指定兩個坐標點

    /*** @Route("/box", name="box")* @Template()*/ public function boxAction(){$request = $this->getRequest();$longitude = (float)$request->get('lon',121.462035);$latitude = (float)$request->get('lat',31.237641);$longitude2 = (float)$request->get('lon2',121.522098);$latitude2 = (float)$request->get('lat2',31.215284);$places = $this->getPlaceRepository()->createQueryBuilder()->field('coordinate')->withinBox($longitude, $latitude, $longitude2, $latitude2)->getQuery()->toarray();return compact('places','longitude','latitude', 'longitude2', 'latitude2'); }

    通過 domain.dev/box 訪問,效果如下:

    圓形區域

    應用場景有:地圖搜索租房信息

    查詢以某坐標為圓心,指定半徑的圓內的數據。

    前面已提到,圓形區域搜索分為$center和$centerSphere這兩種類型,它們的區別主要在于支持的索引和默認距離單位不同。

    2d索引能同時支持$center和$centerSphere,2dsphere索引支持$centerSphere。關于距離單位,$center默認是度,$centerSphere默認距離是弧度。

    查詢方式如下:

    > db.places.find({'coordinate':{$geoWithin:{$centerSphere:[ [121.4905, 31.2646] , 0.6/111] }}}) 或 > db.places.find({'coordinate':{$geoWithin:{$centerSphere:[ [121.4905, 31.2646] , 0.6/6371] }}}) 查詢結果 { "_id" : 115, "coordinate" : { "longitude" : 121.4915, "latitude" : 31.25933 }, "title" : "僅售148元,市場價298元的星程上服假日酒店全日房一間入住一天,節假日通用, 精致生活,品質享受", "address" : "虹口區天水路90號" } ...

    Symfony2演示代碼:

    指定圓心坐標和半徑

    /*** @Route("/center", name="center")* @Template()*/ public function centerAction(){$request = $this->getRequest();$longitude = (float)$request->get('lon',121.4905);$latitude = (float)$request->get('lat',31.2646);//10km$max = (float)$request->get('max', 10);$places = $this->getPlaceRepository()->createQueryBuilder()->field('coordinate')->withinCenter($longitude, $latitude, $max/111)->getQuery()->toarray();return compact('places','max','longitude','latitude'); }

    通過 domain.dev/center 訪問,效果如下:

    以longitude: xxx,latitude: xxx為中心點,半徑10km的圓內

    多邊形

    復雜區域內的查詢,這個應用場景比較少見。指定至少3個坐標點,查詢方式如下(五邊形):

    > db.places.find( { coordinate : { $geoWithin : { $polygon : [ [121.45183 , 31.243816] ,[121.533181, 31.24344] ,[121.535049, 31.208983] ,[121.448955, 31.214913] ,[121.440619, 31.228748] ] } } } )

    查詢結果

    { "_id" : 90078, "title" : "僅售9.9元,市場價38元的燕太太燕窩單人甜品餐,用耐心守候一盅燉品,用愛滋補一生情誼", "address" : "河南南路489號香港名都購物廣場1F125燕太太燕窩", "coordinate" : { "longitude" : 121.48912, "latitude" : 31.22355 } } ...

    Symfony2演示代碼(這里為方便,直接寫死了5個坐標點):

    /*** @Route("/polygon", name="polygon")* @Template()*/ public function polygonAction(){$points = [];$points[] = [121.45183,31.243816];$points[] = [121.533181,31.24344];$points[] = [121.535049,31.208983];$points[] = [121.448955,31.214913];$points[] = [121.440619,31.228748];$sumlon = $sumlat = 0;foreach($points as $p){$sumlon += $p[0];$sumlat += $p[1];}$center = [$sumlon/count($points), $sumlat/count($points)];$places = $this->getPlaceRepository()->createQueryBuilder()->field('coordinate')->withinPolygon($points[0], $points[1], $points[2], $points[3], $points[4])->getQuery()->toarray();return compact('places','points', 'center'); }

    通過 domain.dev/polygon 訪問,效果如下:

    案例C:附近的餐廳

    我們假設需要以當前坐標為原點,查詢附近指定范圍內的餐廳,并直接顯示距離。

    這個需求用前面提到的$near是可以實現的,但是距離需要二次計算。這里我們用$geoNear這個命令查詢。

    $geoNear與$near功能類似,但提供更多功能和返回更多信息,官方文檔是這么解釋的:

    The geoNear command provides an alternative to the $near operator. In addition to the functionality of $near, geoNear returns additional diagnostic information.

    查詢方式如下(關于下面的示例用到了distanceMultipler函數,后文會詳細解釋):

    > db.runCommand( { geoNear: "places", near: [ 121.4905, 31.2646 ], spherical: true,maxDistance:1/6371, num:2 }) {"ns" : "mongo_test.places","near" : "1110001100111100001011010110010111001000110011111101","results" : [{"dis" : 0.00009318095248858048,"obj" : {"_id" : 115,"coordinate" : {"longitude" : 121.4915,"latitude" : 31.25933},"title" : "僅售148元,市場價298元的星程上服假日酒店全日房一間入住一天, 節假日通用,精致生活,品質享受","address" : "虹口區天水路90號"}},{"dis" : 0.00010610660597329082,"obj" : {"_id" : 465,"coordinate" : {"longitude" : 121.48406,"latitude" : 31.26202},"title" : "【四川北路】熱烈慶祝康駿會館成立8周年!僅售169元!市場價399元的 康駿會館四川北路一店(僅限3星級技師)全身精油按摩一人次!全程約90分鐘! 男女不限!僅限四川北路一店使用,非本市所有門店通用!拉手券消費僅限每日19:00前! 健康有道,駿越萬里!","address" : "虹口區四川北路1896號-1904號201室"}}],"stats" : {"time" : 0,"btreelocs" : 0,"nscanned" : 18,"objectsLoaded" : 12,"avgDistance" : 0.00009964377923093564,"maxDistance" : 0.0001064199324957278},"ok" : 1 }

    可以看到返回了很多詳細信息,如查詢時間、返回數量、最大距離、平均距離等。

    另外,results里面直接返回了距離目標點的距離dis。

    Symfony2演示代碼:

    /*** @Route("/distance", name="distance")* @Template()*/ public function distanceAction(){$longitude = (float)$this->getRequest()->get('lon',121.4905);$latitude = (float)$this->getRequest()->get('lat',31.2646);//2km$max = (float)$this->getRequest()->get('max', 2);$places = $this->getPlaceRepository()->createQueryBuilder()->field('coordinate')->geoNear($longitude, $latitude)->spherical(true)->distanceMultiplier(6371)->maxDistance($max/6371)->limit(100)->getQuery()->execute()->toArray();return compact('places','longitude', 'latitude', 'max'); }

    通過 domian.dev/distance 訪問,效果如下:

    距離xxx米

    小結

    前面演示的查詢代碼中,坐標都是按照 longitude, latitude這個順序的。

    這個是官方建議的坐標順序,但是網上很多文檔是相反的順序,經測試發現,只要查詢時指定的坐標順序與數據庫內的坐標順序一致,出來的結果就是正確的,沒有特定的先后順序之分。

    但鑒于官方文檔的推薦,我在此還是建議大家按照官方推薦的順序。

    案例A的$near和案例B的$center從需求上看差不多,但是$center或$centerSphere是屬于$geoWithin的類型,$near方法查詢后會對結果集對距離進行排序,而$geoWithin是無序的。

    常用的查詢方式已經介紹完了,不常用的比如geoIntersect查詢,這里不做介紹,但是已經包含在開源的演示程序里了,有興趣的讀者可以自行測試研究。

    下面介紹前文提到的距離單位等問題。

    5. 需要注意的問題

    索引

    $near命令必須要求有索引。

    $geoWithin可以無需索引,但是建議還是建立索引以提升性能。

    距離單位

    MongoDB查詢地理位置默認有3種距離單位:

    • 米(meters)
    • 平面單位(flat units,可以理解為經緯度的“一度”)
    • 弧度(radians)。

    通過GeoJSON格式查詢,單位默認是米,通過其它方式則比較混亂,下面詳細解釋一下。

    下面的查詢語句指定距離內的目標:

    > db.places.find({'coordinate':{$near: [121.4905, 31.2646], $maxDistance:2}})

    現在$maxDistance參數是2,但是如果我要查詢如“附近500米內的餐廳”這樣的需求,這個參數應該是多少?

    關于距離計算,MongoDB的官方文檔僅僅提到了弧度計算,未說明水平單位(度)計算。

    關于弧度計算,官方文檔的說明是:

    To convert: distance to radians: divide the distance by the radius of the sphere (e.g. the Earth) in the same units as the distance measurement. radians to distance: multiply the radian measure by the radius of the sphere (e.g. the Earth) in the units system that you want to convert the distance to.?

    The radius of the Earth is approximately 3,959 miles or 6,371 kilometers.

    所以如果用弧度查詢,則以公里數除以6371,如“附近500米的餐廳”:

    > db.runCommand( { geoNear: "places", near: [ 121.4905, 31.2646 ], spherical: true,$maxDistance: 0.5/6371 })

    那如果不用弧度,以水平單位(度)查詢時,距離單位如何處理?

    答案是以公里數除以111(推薦值),原因如下:

    經緯度的一度,分為經度一度和緯度一度。

    地球不同緯度之間的距離是一樣的,地球子午線(南極到北極的連線)長度39940.67公里,緯度一度大約110.9公里

    但是不同緯度的經度一度對應的長度是不一樣的:

    在地球赤道,一圈大約為40075KM,除以360度,每一個經度大概是:40075/360=111.32KM

    上海,大概在北緯31度,對應一個經度的長度是:40075*sin(90-31)/360=95.41KM

    北京在北緯40度,對應的是85KM

    前面提到的參數111,這個值只是估算,并不完全準確,任意兩點之間的距離,平均緯度越大,這個參數則誤差越大。詳細原因可以參考wiki上的解釋:http://en.wikipedia.org/wiki/Latitude

    但是,即便如此,“度”這個單位只用于平面,由于地球是圓的,在大范圍使用時會有誤差。

    官方建議使用sphere查詢方式,也就是說距離單位用弧度。

    The current implementation assumes an idealized model of a flat earth, meaning that an arcdegree of latitude (y) and longitude (x) represent the same distance everywhere. This is only true at the equator where they are both about equal to 69 miles or 111km. However, at the 10gen offices at { x : -74 , y : 40.74 } one arcdegree of longitude is about 52 miles or 83 km (latitude is unchanged). This means that something 1 mile to the north would seem closer than something 1 mile to the east.

    $geoNear返回結果集中的dis,如果指定了spherical為true, dis的值為弧度,不指定則為度。

    指定 spherical為true,結果中的dis需要乘以6371換算為km:

    > db.runCommand( { geoNear: "places", near: [ 121.4905, 31.2646 ], spherical: true, num:1 }) {"ns" : "mongo_test.places","near" : "1110001100111100001011010110010111001000110011111101","results" : [{"dis" : 0.00009318095248858048,"obj" : {"_id" : 115,"coordinate" : {"longitude" : 121.4915,"latitude" : 31.25933},"title" : "僅售148元,市場價298元的星程上服假日酒店全日房一間入住一天,節假日通用, 精致生活,品質享受","address" : "虹口區天水路90號"}}],"stats" : {"time" : 0,"btreelocs" : 0,"nscanned" : 18,"objectsLoaded" : 12,"avgDistance" : 0.00009964377923093564,"maxDistance" : 0.0001064199324957278},"ok" : 1 }

    不指定sphericial,結果中的dis需要乘以111換算為km:

    > db.runCommand( { geoNear: "places", near: [ 121.4905, 31.2646 ], num:1 }) {"ns" : "mongo_test.places","near" : "1110001100111100001011010110010111001000110011111101","results" : [{"dis" : 0.005364037658335473,"obj" : {"_id" : 115,"coordinate" : {"longitude" : 121.4915,"latitude" : 31.25933},"title" : "僅售148元,市場價298元的星程上服假日酒店全日房一間入住一天,節假日通用, 精致生活,品質享受","address" : "虹口區天水路90號"}} ],"stats" : {"time" : 0,"btreelocs" : 0,"nscanned" : 18,"objectsLoaded" : 12,"avgDistance" : 0.006150808243357531,"maxDistance" : 0.00695541352612983},"ok" : 1 }

    說到這里讀者是不是已經有點迷糊了?沒關系,在開發中其實你并不需要去知道各種距離單位的歷史和使用它的原因,我在此為你總結了一張表,大部分常用的函數和所使用的距離單位都已經被我整理了出來,你只需要參考表上所列的距離單位直接使用即可。

    查詢命令距離單位說明
    $near官方文檔上關于這一點是錯的
    $nearSphere弧度?
    $center?
    $centerSphere弧度?
    $polygon?
    $geoNear度或弧度指定參數spherical為true則為弧度,否則為度

    如果坐標以GeoJSON格式,則單位都為米。

    當然如果你的操作比較復雜,或者希望知道更加詳細的對照關系,也可以參考官方的這個更詳細的對比表格:http://docs.mongodb.org/manual/reference/operator/query-geospatial/

    單位自動換算

    如上面兩個geoNear示例,結果中的dis,前文已經提過這是與目標點的距離,但是這個距離單位是跟查詢單位一致的,需要二次計算,不太方便。

    而其實可以直接在查詢時指定?distanceMultiplier?,它會將這個參數乘以距離返回,如指定為6371,返回的就是公里數。

    > db.runCommand({ geoNear : "places", near : [121.4905, 31.2646], spherical : true,maxDistance : 1/6371, distanceMultiplier: 6371}) {"ns" : "mongo_test.places","near" : "1110001100111100001011010110010111001000110011111101","results" : [{"dis" : 0.5936558483047463,"obj" : {"_id" : 115,"coordinate" : {"longitude" : 121.4915,"latitude" : 31.25933},"title" : "僅售148元,市場價298元的星程上服假日酒店全日房一間入住一天,節假日通用, 精致生活,品質享受","address" : "虹口區天水路90號"}},……],"stats" : {"time" : 0,"btreelocs" : 0,"nscanned" : 15,"objectsLoaded" : 9,"avgDistance" : 0.6348305174802911,"maxDistance" : 0.0001064199324957278},"ok" : 1 }

    注意上面的結果中dis的值,已經是km單位的了。

    結語

    通過前面的案例演示,相信大家對MongoDB的地理位置特性已經比較了解。

    MongoDB還有很多很酷的功能,地址位置支持僅是其中一項。希望以后能有機會為各位讀者介紹如何結合Symfony2使用MongoDB進行應用開發的更多案例。

    文中的演示程序已經發布在了Github上,地址是https://github.com/henter/HenterGEO,讀者可以直接使用。

    參考:

    http://docs.mongodb.org/manual/

    https://wiki.10gen.com/pages/viewpage.action?pageId=21268367&navigatingVersions=true

    http://en.wikipedia.org/wiki/Radian

    http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL

    http://www.phpchina.com/resource/manual/mysql/spatial-extensions-in-mysql.html

    http://derickrethans.nl/spatial-indexes-mysql.html

    http://dev.mysql.com/doc/refman/5.6/en/spatial-extensions.html

    http://dev.mysql.com/doc/refman/4.1/en/functions-that-test-spatial-relationships-between-geometries.html#function_distance

    http://blog.nosqlfan.com/html/1811.html

    http://en.wikipedia.org/wiki/Geohash

    總結

    以上是生活随笔為你收集整理的结合MongoDB开发LBS应用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。