lbs(查看附近的人),看看社交軟件如何實現查看附近的人


        最近在做一款移動端棋牌游戲,為了進一步提高用戶體驗、拉近玩家的距離,我們決定在游戲中加入好友功能,而對於體驗好友功能的玩家來說,要是玩牌的時候可以看看附近都有誰在玩牌,跟他們交流交流玩牌心得什么的無疑是個不錯的想法。而要實現查看附近的人就需要提提LBS(Location Based Service),他的意思就是基於位置的服務,就是通過移動終端獲取到許多用戶或者物體的經緯度坐標,通過這些位置信息所提供的服務。

       好了,扯了這么多,我們來看看如何實現查看附近人的功能的:

       首先要具備下面這些環境:

    1. php+MySQL(MySQL不是必須,本文中用的是redis來存儲用戶的信息)
    2. redis(本文用的是redis,當然你也可以用MySQL)
    3. geohash.class.php類(這個類是用來處理經緯度坐標的一些基本函數,當然這些東西完全可以自己去寫,如果時間充裕的話)

       好了,等這些環境都具備了之后,我來講講這個實現過程:

  • 首先介紹下GeoHash思想

      第一步.  編碼

      這個功能應用到了一個很好的算法GeoHash,也許有同學聽過這個功能,沒錯GeoHash就是通過一個巧妙的算法(不由得驚嘆前輩們真牛!)把經緯度轉化為字符串,這樣有什么好處呢,顯而易見,將二維的數據轉化為了一維,這樣一來存儲就方便了,搜索效率也會高很多,那么現在問題來了,GeoHash算法是如何把經緯度坐標轉化為字符串的?

      將經緯度編碼為字符串的過程可以分為以下3個步驟:

      首先就是編碼,對於經緯度的編碼通過折半比較法,當大於中值時該位編碼為1(小於時編碼為0),下次新的區間為中值到最大值(或者最小值到中值),這樣一直比較下去,直到到達要求的精度,精度和緯度的方法是一樣的,只不過一個原始區間是(-90,90),一個是(-180,180),光說不好理解,下面我們看看一個簡單的例子:

對經度32.165進行編碼:                                                                                                 對緯度89.156進行編碼:

編碼

min

mid

max

1

-90

0

90

0

0

45

90

1

0

22.5

45

0

22.5

033.75

45

1

22.5

28.125

33.75

1

28.125

30.9375

33.75

0

30.9375

32.34375

33.75

1

30.9375

31.640625

32.34375

1

31.640625

31.9921875

32.34375

0

31.9921875

32.16796875

32.34375

編碼

min

mid

max

1

-180

0

180

0

0

90

180

1

0

45

90

1

45

67.5

90

1

67.5

78.75

90

1

78.75

84.375

90

1

84.375

87.1875

90

1

87.1875

88.59375

90

0

88.59375

89.296875

90

1

88.59375

88.9453125

89.296875

        這樣便可將(89.156,32.165) =>  (10101 10110,10111 11101)

        這個時候就到了第二步驟——組碼了,顧名思義,將第一步產生的編碼組合起來為下一步產生字符串做准備,組碼的方式是偶數位放置經度,奇數位放緯度(為什么要這么做呢,我猜可能是谷歌為了大家統一規范,僅此而已,其實奇偶數位互換也可以的),對於上面的經緯度編碼后再組碼如下:

經度:10101 11101

緯度:10111 11101

位置編碼:11001 11011 11111 10011

圖 3

       對於上面的位置編碼,為什么要這么編碼呢,為什么要奇數位放緯度,偶數為方經度呢,我們看看下面這張圖,用這張圖模擬地圖的經緯度,A點(-180,90),B點(180,90),C點(-180,-90),D點(180,-90);

A

 

 

 

 

 

 

 

 

B

 

010101

010111

011101

011111

110101

110111

111101

111111

 

 

010100

010110

011100

011110

110100

110110

111100

111110

 

 

010001

010011

011001

011011

110001

110011

111001

111011

 

 

010000

010010

011000

011010

110000

110010

111000

111010

 

 

000101

000111

001101

001111

100101

100111

101101

101111

 

 

000100

000110

001100

001110

100100

100110

101100

101110

 

 

000001

000011

001001

001011

100001

100011

101001

101011

 

 

000000

000010

001000

001010

100000

100010

101000

101010

 

C

 

 

 

 

 

 

 

 

D

圖 4

        如圖4所示,這樣就可以將地圖(經度-180~180,緯度-90~90)分為很多很多多的小塊,每一個小塊都有唯一的二進制編碼,當位數達到一定的長度時就可以表示很小的一塊區域,這不就可以根據二進制編碼定位一個唯一的位置了嗎,對於划分的進一步理解可看下面的圖。

geohash
圖 5
      如圖5所示,左邊是是對緯度(-180,180)的划分,可以看出通過划分可以確定(22.4,45)這一緯度區間的編碼為1001,當然了位數越多精度越高,同理對經度進行划分,可以確定(-78.75,-67.5)這一經度范圍的編碼為0001,可以想象,當左右兩張圖合在一起時就可以確定一個唯一的矩形區域,當該區域足夠小的時候就可一看做一個點。
 
      第二步.  組碼
      從圖3可以看出我們對經緯度編碼后可得二進制字符串11001 11011 11111 10011
      最后使用用0-9、b-z(去掉a, i, l, o)這32個字母進行base32編碼,首先將11001 11011 11111 10011轉成十進制,對應着25、27、32、19,十進制對應的編碼就是tvzm。同理,將編碼轉換成經緯度的解碼算法與之相反,具體不再贅述。至此,我們的對geohash有了個大致的了解。

        圖 6

  • 如何具體的應用到程序中

       首先思考一下查看附近的人的流程:

    1. 用戶點擊查看附近的人按鈕,首先獲取到該用戶的選位置信息(經緯度),傳給服務器。
    2. 服務器收到數據之后對該用戶的位置信息進行geohash計算,獲得該用戶的位置hash字符串。
    3. 對該用戶的位置信息hash串進行緩存(緩存時間長短根據具體情況而定)。
    4. 根據該hash串選出附近的人。
    5. 對hash進行解碼,計算出附近用戶的位置,返回給用戶。

       首先看看geohash.class.php這個公共類庫里面的基本方法:

 

[public]Geohash()       初始化hash映射表

Geohash

[public]encode($lat,$long)       對經緯度進行編碼

 

[public]decode()       對hash進行解碼

圖 7

      如圖7所示,顯而易見這個類庫里面有3個函數,第一個用來初始化hash映射表,其實就是把0123456789bcdefghjkmnpqrstuvwxyz字符串中的每個字符和它對應的二進制編碼對應起來(左邊補零至5位)。encode()是用來生成hash的,decode是用來解碼hash得到hash對應的經緯度的。

        下面我們看個例子,現在假設有圖8中的幾個用戶查看附近的人:

mid

坐標

100

(42.61233,-5.61234)

101

(-20.25689, 50.56897)

102

(10.11233, 57.21234)

103

(49.26343, -123.26895)

104

(0.00534, -179.56732)

105

(-30.55555, 0.28958)

106

(5.00001, -140.63422)

107

(42.61234, -5.61234)

108

(5.00001, -140.63422)

圖 8

       圖8的數據發送到服務器經過geohash計算得出下面的hash表:

 

mid

坐標

100

ezs42m34yfp_100

101

mh7uy8r5n6j_101

102

t3b9tbuu84u_102

103

c2b26bnk32b_103

104

80021bgp45m_104

105

k484ntdc58w_105

106

8bgury1r1jm_106

107

ezs42m34ygz_107

108

8bgury1r1jm_108

圖 9

        計算出這些hash值,將hash值存入redis中,存入redis中之后,那么問題來了,如何去獲取一個用戶附近的用戶呢?當redis數據庫中有了一些用戶的記錄之后,來一個用戶,我們先對其進行編碼,然后根據該用戶的位置hash從redis中選出該用戶附近的hash,選取附近的hash這一步很簡單,對於redis只需這么做:

<?php
       $mid = 2014;
       $level = 7;    //獲取的精度等級,數字越大,附近這個范圍越小
        $redis = Redis::init();  //假設這樣獲取到redis實例
        $mykey = '8gur95yjmz';  //假設我的hash為這個
        $redis->setex($mykey.'_'.$mid,$_SERVER['REQUEST_TIME'],86400);    //這里設置緩存1天,具體情況具體對待
$search = substr($makey,0,$level);
       $nearbys = $redis->keys("{$search}*");
?>

      程序 1

       上面的幾句代碼就可以選出我附近的人的hash,當然,其中的level來設置精度的,這個數字越大,附近的人范圍越小,具體參考圖10中的值,這個表中的值是從我的導師李偉(weickly)那里獲取到的。要注意,這個地方搜索完之后要排除自己。還有一點要注意,就是在緩存時鍵名的最后一定要加上_{$mid},這樣做可以避免多個用戶在同一位置是互相覆蓋的情況(就像圖9中mid為106和108的用戶),放在最后是為了不影響搜索。

1

2500000m

2

630000m

3

78000m

4

20000m

5

2400m

6

610m

7

76m

8

19m

9

2m

圖 10
        例如mid為109,經緯度為(42.61236, -5.61234)的用戶,當他點擊獲取附近的人按鈕式,我獲取到他的經緯度並計算出他的hash,$mykey='ezs42m34yzx',然后通過程序段1可以獲取到他附近的hash:

100

ezs42m34yfp_100

(42.61233,-5.61234)

3.3m

107

ezs42m34ygz_107

(42.61234, -5.61234)

2.2m

109

ezs42m34yzx_109

(42.61236, -5.61234)

0m

圖 11
        圖11中獲取到了用戶109附近的用戶hashs,獲取到hash值還並沒有完成,首先排除掉自己109那條記錄,然后通過Geohash類中的decode將hash解碼為經緯度,通過每個用戶的經緯度計算出和109用戶的距離,然后按距離等級返回,比如說小於100,小於200……
        至此,獲取附近的人就完成了,當然了具體實踐的時候還要隨機應變具體情況具體對待,我寫這篇文章只是想起到拋磚引玉的效果,本文中可能會存在很多不足,還望斧正。
 
  本文版權歸作者(luluyrt@163.com)和博客園共有,未經作者本人同意禁止任何形式的轉載,轉載文章之后必須在文章頁面明顯位置給出作者和原文連接,否則保留追究法律責任的權利。 
 


免責聲明!

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



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