多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內(nèi)最全I(xiàn)T社區(qū)平臺 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁 > 數(shù)據(jù)庫 > access > 深入淺出Symfony2 - 結(jié)合MongoDB開發(fā)LBS應(yīng)用

深入淺出Symfony2 - 結(jié)合MongoDB開發(fā)LBS應(yīng)用

來源:程序員人生   發(fā)布時間:2014-10-18 08:00:00 閱讀次數(shù):5892次

http://www.infoq.com/cn/articles/depth-study-of-Symfony2

簡介

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

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

本文使用了Symfony2和Doctrine2作為Web應(yīng)用的開發(fā)框架,對于想了解Symfony2的數(shù)據(jù)庫操作的讀者來說閱讀本文也可以了解和掌握相關(guān)的技術(shù)和使用方法。

 


不管是什么LBS應(yīng)用,一個共同的特點(diǎn)就是:他們的數(shù)據(jù)都或多或少包含了地理位置信息。而如何對這些信息進(jìn)行查詢、處理、分析,也就成為了支撐LBS應(yīng)用的最基礎(chǔ)也是最關(guān)鍵的技術(shù)問題。1. LBS類應(yīng)用特點(diǎn)

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

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

 

而最近幾年,直接支持地理位置操作的數(shù)據(jù)庫層出不窮,其操作友好、性能高的特性也開始被我們慢慢重視起來,其中的佼佼者當(dāng)屬M(fèi)ongoDB。

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

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

1. 確定功能需求

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

我們假設(shè)有如下功能需求:

  • 顯示我附近的人
  • 由近到遠(yuǎn)排序
  • 顯示距離

2. 可能的技術(shù)方案

排除一些不通用和難以實(shí)現(xiàn)的技術(shù),我們羅列出以下幾種方案:

  1. 基于MySQL數(shù)據(jù)庫
  2. 采用GeoHash索引,基于MySQL
  3. MySQL空間存儲(MySQL Spatial Extensions)
  4. 使用MongoDB存儲地理位置信息

我們一個個來分析這幾種方案。

方案1:基于MySQL數(shù)據(jù)庫

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

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

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

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

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

先別放棄,我們嘗試對這條SQL做一些優(yōu)化。

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

根據(jù)維基百科上的球面計(jì)算公式,可以根據(jù)圓心坐標(biāo)計(jì)算出正方形四個點(diǎn)的坐標(biāo)。

然后,查詢這個正方形內(nèi)的目標(biāo)點(diǎn)。

SQL語句可以簡化如下:

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

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

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

方案2:GeoHash索引,基于MySQL

GeoHash是一種地址編碼,通過切分地圖區(qū)域?yàn)樾》綁K(切分次數(shù)越多,精度越高),它能把二維的經(jīng)緯度編碼成一維的字符串。也就是說,理論上geohash字符串表示的并不是一個點(diǎn),而是一個矩形區(qū)域,只要矩形區(qū)域足夠小,達(dá)到所需精度即可。(其實(shí)MongoDB的索引也是基于geohash)

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

所以這樣一來,我們就可以在MySQL中用LIKE ‘wtw3u%’來限定區(qū)域范圍查詢目標(biāo)點(diǎn),并且可以對結(jié)果集做緩存。更不會因?yàn)槲⑿〉慕?jīng)緯度變化而無法用上數(shù)據(jù)庫的Query Cache。

這種方案的優(yōu)點(diǎn)顯而易見,僅用一個字符串保存經(jīng)緯度信息,并且精度由字符串從頭到尾的長度決定,可以方便索引。

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

除此之外,這個方案也無法直接得到距離,需要程序協(xié)助進(jìn)行后續(xù)的排序計(jì)算。

方案3:MySQL空間存儲

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

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

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

這條語句的處理邏輯是先通過兩個點(diǎn)產(chǎn)生一個LineString的類型的數(shù)據(jù),然后調(diào)用GLength得到這個LineString的實(shí)際長度。

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

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

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

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

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

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

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

查詢結(jié)果默認(rèn)將會由近到遠(yuǎn)排序,而且查詢結(jié)果也包含目標(biāo)點(diǎn)對象、距離目標(biāo)點(diǎn)的距離等信息。

由于geoNear是MongoDB原生支持的查詢函數(shù),所以性能上也做到了高度的優(yōu)化,完全可以應(yīng)付生產(chǎn)環(huán)境的壓力。

方案總結(jié)

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

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

接下來,我們結(jié)合Symfony2來詳細(xì)地演示一些使用MongoDB進(jìn)行地理位置信息處理的例子。

3. 結(jié)合Symfony2演示

運(yùn)行環(huán)境

參考環(huán)境:Nginx1.2 + PHP5.4 + MongoDB2.4.3 + Symfony2.1

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

Document/Coordinate.php /** * @MongoDBEmbeddedDocument */ class Coordinate { /** * @MongoDBField(type="float") */ public $longitude; /** * @MongoDBField(type="float") */ public $latitude; ... } Document/Place.php /** * @MongoDBDocument(collection="places") * @MongoDBChangeTrackingPolicy("DEFERRED_EXPLICIT") * @MongoDBIndexes({ * @MongoDBIndex(keys={"coordinate"="2d"}), * @MongoDBIndex(keys={"coordinate"="2dsphere"}) * }) */ class Place { /** * * @MongoDBId(strategy="INCREMENT") */ protected $id; /** * @MongoDBField(type="string") */ protected $title; /** * @MongoDBField(type="string") */ protected $address; /** * @MongoDBEmbedOne(targetDocument="HenterGEOGEOBundleDocumentCoordinate") */ protected $coordinate; /** * @MongoDBDistance */ public $distance; ... }

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

另外,為直觀顯示查詢效果,默認(rèn)使用百度地圖標(biāo)記查詢數(shù)據(jù)。

程序說明

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

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

ODM中的操作默認(rèn)距離單位是度,只有g(shù)eoSphere支持弧度單位(必須在參數(shù)中指定spherical(true))

4. MongoDB的地理位置查詢

注意事項(xiàng)

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

地理位置索引:

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

  • 2d 平面坐標(biāo)索引,適用于基于平面的坐標(biāo)計(jì)算。也支持球面距離計(jì)算,不過官方推薦使用2dsphere索引。
  • 2dsphere 幾何球體索引,適用于球面幾何運(yùn)算

關(guān)于兩個坐標(biāo)之間的距離,官方推薦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.

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

建立索引:

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

查詢方式:

查詢方式分三種情況:

  1. Inclusion。范圍查詢,如百度地圖“視野內(nèi)搜索”。
  2. Inetersection。交集查詢。不常用。
  3. Proximity。周邊查詢,如“附近500內(nèi)的餐廳”。

而查詢坐標(biāo)參數(shù)則分兩種:

  1. 坐標(biāo)對(經(jīng)緯度)根據(jù)查詢命令的不同,$maxDistance距離單位可能是 弧度 和 平面單位(經(jīng)緯度的“度”):

    db..find( { : { $nearSphere: [ , ] , $maxDistance: } } )
  2. GeoJson $maxDistance距離單位默認(rèn)為米:

    db..find( { : { $nearSphere : { $geometry : { type : "Point" , coordinates : [ , ] } , $maxDistance : } } } )

案例A:附近的人

查詢當(dāng)前坐標(biāo)附近的目標(biāo),由近到遠(yuǎn)排列。

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

查詢方式:

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

查詢結(jié)果:

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

上述查詢坐標(biāo)[121.4905, 31.2646]附近的100個點(diǎn),從最近到最遠(yuǎn)排序。

默認(rèn)返回100條數(shù)據(jù),也可以用limit()指定結(jié)果數(shù)量,如

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

指定最大距離 $maxDistance

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

結(jié)合Symfony2進(jìn)行演示:

這里用near,默認(rèn)以度為單位,公里數(shù)除以111(關(guān)于該距離單位后文有詳細(xì)解釋)。

/** * @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為當(dāng)前位置,我們在地圖上顯示了周邊100條目標(biāo)記錄

案例B:區(qū)域內(nèi)搜索

MongoDB中的

生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 日韩欧美精品中文字幕 | 天天天狠天天透天天制色 | 男人边吃奶边做好爽的视频 | 国产成人精品在视频 | 黄色在线网站视频 | 亚洲国产一区二区三区综合片 | 亚洲免费网 | 最近中文字幕视频在线资源 | 一级在线观看视频 | 无人精品乱码一区二区三区 | 欧美xxxx极品流血 | 亚洲精品欧美精品一区二区 | 欧美一区二区三区在线可观看 | 男女上下爽无遮挡午夜免费视频 | 免费xx| 国产精品第4页 | yellow中文字幕官网是什么 | 69免费视频大片 | 国产成人精品免费视频网页大全 | 国内精品免费一区二区三区 | 日本不卡高清中文字幕免费 | 国产一区二区三区四区五区六区 | 不卡的毛片 | 亚洲伊人久久大香线蕉影院 | 91sao国产在线观看 | 国产成人在线视频 | 中文字幕精品一区二区2021年 | 久久天天躁夜夜躁狠狠躁2020 | 国产精品成人观看视频网站 | 图片区 小说区 | 永久免费毛片在线播放 | 国产欧美日韩图片一区二区 | 亚洲国产精品乱码在线观看97 | 日本高清在线看 | 性欧美videos另类hd | 在线不卡免费视频 | 夜夜精品视频一区二区 | 波多野结衣与老人公gvg在线 | 国内精自视频品线六区免费 | 中文字幕第一区 | 一级欧美在线的视频 |