PHP-Geohash算法查找附近的人

最近需要做一個需求,查看動態(tài)時需要查找附近的人動態(tài)督禽,因此呢就研究了下Geohash算法脆霎。
一、優(yōu)點:
1赂蠢、利用一個字段绪穆,即可存儲經(jīng)緯度;搜索時虱岂,只需一條索引玖院,效率較高
2、編碼的前綴可以表示更大的區(qū)域第岖,查找附近的难菌,非常方便。 SQL中蔑滓,LIKE 'wm3yr3%'郊酒,即可查詢附近的所有地點。
二键袱、直接上代碼(Geohash算法具體可自行百度)

<?php
/**
 * Geohash
 * @author PwordC
 */

class Geohash {

    private $bitss = array(16, 8, 4, 2, 1);
    private $neighbors = array();
    private $borders = array();
    
    private $coding = "0123456789bcdefghjkmnpqrstuvwxyz";
    private $codingMap = array();

    /**
     * 位數(shù)范圍(單位km)
     * 1    ±2500
     * 2    ±630
     * 3    ±78
     * 4    ±20
     * 5    ±2.4
     * 6    ±0.61
     * 7    ±0.076
     * 8    ±0.01911
     * 9    ±0.00478
     * 10   ±0.0005971
     * 11   ±0.0001492
     * 12   ±0.0000186
     */
    
    public function Geohash() {
        
        $this->neighbors['right']['even'] = 'bc01fg45238967deuvhjyznpkmstqrwx';
        $this->neighbors['left']['even'] = '238967debc01fg45kmstqrwxuvhjyznp';
        $this->neighbors['top']['even'] = 'p0r21436x8zb9dcf5h7kjnmqesgutwvy';
        $this->neighbors['bottom']['even'] = '14365h7k9dcfesgujnmqp0r2twvyx8zb';
        
        $this->borders['right']['even'] = 'bcfguvyz';
        $this->borders['left']['even'] = '0145hjnp';
        $this->borders['top']['even'] = 'prxz';
        $this->borders['bottom']['even'] = '028b';
        
        $this->neighbors['bottom']['odd'] = $this->neighbors['left']['even'];
        $this->neighbors['top']['odd'] = $this->neighbors['right']['even'];
        $this->neighbors['left']['odd'] = $this->neighbors['bottom']['even'];
        $this->neighbors['right']['odd'] = $this->neighbors['top']['even'];
        
        $this->borders['bottom']['odd'] = $this->borders['left']['even'];
        $this->borders['top']['odd'] = $this->borders['right']['even'];
        $this->borders['left']['odd'] = $this->borders['bottom']['even'];
        $this->borders['right']['odd'] = $this->borders['top']['even'];
        
        for($i=0; $i<32; $i++) {
        
            $this->codingMap[substr($this->coding, $i, 1)] = str_pad(decbin($i), 5, "0", STR_PAD_LEFT);
        }
        
    }

    public function decode($hash) {
    
        $binary = "";
        $hl = strlen($hash);
        for ($i=0; $i<$hl; $i++) {
        
            $binary .= $this->codingMap[substr($hash, $i, 1)];
        }
        
        $bl = strlen($binary);
        $blat = "";
        $blng = "";
        for ($i=0; $i<$bl; $i++) {
        
            if ($i%2)
                $blat=$blat.substr($binary, $i, 1);
            else
                $blng=$blng.substr($binary, $i, 1);
            
        }
        
        $lat = $this->binDecode($blat, -90, 90);
        $lng = $this->binDecode($blng, -180, 180);
        
        $latErr = $this->calcError(strlen($blat), -90, 90);
        $lngErr = $this->calcError(strlen($blng), -180, 180);
                
        $latPlaces = max(1, -round(log10($latErr))) - 1;
        $lngPlaces = max(1, -round(log10($lngErr))) - 1;
        
        $lat = round($lat, $latPlaces);
        $lng = round($lng, $lngPlaces);

        return array($lat, $lng);
    }

    
    private function calculateAdjacent($srcHash, $dir) {
    
        $srcHash = strtolower($srcHash);
        $lastChr = $srcHash[strlen($srcHash) - 1];
        $type = (strlen($srcHash) % 2) ? 'odd' : 'even';
        $base = substr($srcHash, 0, strlen($srcHash) - 1);
        
        if (strpos($this->borders[$dir][$type], $lastChr) !== false) {
            
            $base = $this->calculateAdjacent($base, $dir);  
        }
            
        return $base . $this->coding[strpos($this->neighbors[$dir][$type], $lastChr)];
    }
    
    
    public function neighbors($srcHash) {
    
        $geohashPrefix = substr($srcHash, 0, strlen($srcHash) - 1);
     
        $neighbors['top'] = $this->calculateAdjacent($srcHash, 'top');
        $neighbors['bottom'] = $this->calculateAdjacent($srcHash, 'bottom');
        $neighbors['right'] = $this->calculateAdjacent($srcHash, 'right');
        $neighbors['left'] = $this->calculateAdjacent($srcHash, 'left');
        
        $neighbors['topleft'] = $this->calculateAdjacent($neighbors['left'], 'top');
        $neighbors['topright'] = $this->calculateAdjacent($neighbors['right'], 'top');
        $neighbors['bottomright'] = $this->calculateAdjacent($neighbors['right'], 'bottom');
        $neighbors['bottomleft'] = $this->calculateAdjacent($neighbors['left'], 'bottom');
     
        return $neighbors;
    }

    public function encode($lat, $lng) {
    
        $plat = $this->precision($lat);
        $latbits = 1;
        $err = 45;
        while($err > $plat) {
        
            $latbits++;
            $err /= 2;
        }
        
        $plng = $this->precision($lng);
        $lngbits = 1;
        $err = 90;
        while($err > $plng) {
        
            $lngbits++;
            $err /= 2;
        }
        
        $bits = max($latbits, $lngbits);

        $lngbits = $bits;
        $latbits = $bits;
        $addlng = 1;
        while (($lngbits + $latbits) % 5 != 0) {
        
            $lngbits += $addlng;
            $latbits += !$addlng;
            $addlng = !$addlng;
        }
        
        
        $blat = $this->binEncode($lat, -90, 90, $latbits);
        $blng = $this->binEncode($lng, -180, 180, $lngbits);
        
        $binary = "";
        $uselng = 1;
        while (strlen($blat) + strlen($blng)) {
        
            if ($uselng) {
            
                $binary = $binary.substr($blng, 0, 1);
                $blng = substr($blng, 1);
            
            } else {
            
                $binary = $binary.substr($blat, 0, 1);
                $blat = substr($blat, 1);
            }
            
            $uselng = !$uselng;
        }
        
        $hash = "";
        for ($i=0; $i<strlen($binary); $i+=5) {
        
            $n = bindec(substr($binary, $i, 5));
            $hash = $hash.$this->coding[$n];
        }
        
        return $hash;
    }

    private function calcError($bits, $min, $max) {
    
        $err = ($max - $min) / 2;
        while ($bits--)
            $err /= 2;
        return $err;
    }

    private function precision($number) {
    
        $precision = 0;
        $pt = strpos($number,'.');
        if ($pt !== false) {
        
            $precision = -(strlen($number) - $pt - 1);
        }
        
        return pow(10, $precision) / 2;
    }
    
    private function binEncode($number, $min, $max, $bitcount) {
    
        if ($bitcount == 0)
            return "";

        $mid = ($min + $max) / 2;
        if ($number > $mid)
            return "1" . $this->binEncode($number, $mid, $max, $bitcount - 1);
        else
            return "0" . $this->binEncode($number, $min, $mid, $bitcount - 1);
    }
    
    private function binDecode($binary, $min, $max) {
    
        $mid = ($min + $max) / 2;
        
        if (strlen($binary) == 0)
            return $mid;
            
        $bit = substr($binary, 0, 1);
        $binary = substr($binary, 1);
        
        if ($bit == 1)
            return $this->binDecode($binary, $mid, $max);
        else
            return $this->binDecode($binary, $min, $mid);
    }
}






代碼較長燎窘,不關(guān)心算法的情況下可以不關(guān)注具體內(nèi)容,因此沒寫注釋蹄咖。
使用時需要特別注意的一點就是距離范圍
具體使用方法如下:

$geo = new Geohash();
$hash = $geo->encode($lat,$lng);
//默認(rèn)取4位
$prefix = substr($hash, 0, 4);
//取出相鄰區(qū)域,此處為一個數(shù)組區(qū)域
$neighbors = $geo->neighbors($prefix);
//需要將當(dāng)前區(qū)域放進(jìn)數(shù)組中
array_push($neighbors, $prefix);

特別說明一點褐健,距離范圍問題,上述代碼注釋中有說明 ↑↑↑
之后使用sql like 既可以查詢出附近的人的記錄
然后再計算距離即可澜汤;
附距離算法:

/**
 * 根據(jù)起點坐標(biāo)和終點坐標(biāo)測距離
 * @param  [array]   $from  [起點坐標(biāo)(經(jīng)緯度),例如:array(118.012951,36.810024)]
 * @param  [array]   $to    [終點坐標(biāo)(經(jīng)緯度)]
 * @param  [bool]    $km        是否以公里為單位 false:米 true:公里(千米)
 * @param  [int]     $decimal   精度 保留小數(shù)位數(shù)
 * @return [string]  距離數(shù)值
 * @author PwordC
 */
function get_distance($from,$to,$km=true,$decimal=2){
    sort($from);
    sort($to);
    $EARTH_RADIUS = 6370.996; // 地球半徑系數(shù)

    $distance = $EARTH_RADIUS*2*asin(sqrt(pow(sin( ($from[0]*pi()/180-$to[0]*pi()/180)/2),2)+cos($from[0]*pi()/180)*cos($to[0]*pi()/180)* pow(sin( ($from[1]*pi()/180-$to[1]*pi()/180)/2),2)))*1000;

    if($km){
        $distance = $distance / 1000;
    }

    return round($distance, $decimal);
}

具體有問題可以給我留言蚜迅,看到會回復(fù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俊抵,一起剝皮案震驚了整個濱河市谁不,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌徽诲,老刑警劉巖刹帕,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谎替,居然都是意外死亡轩拨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門院喜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亡蓉,“玉大人,你說我怎么就攤上這事喷舀】潮簦” “怎么了淋肾?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長爸邢。 經(jīng)常有香客問我樊卓,道長,這世上最難降的妖魔是什么杠河? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任碌尔,我火速辦了婚禮,結(jié)果婚禮上券敌,老公的妹妹穿的比我還像新娘唾戚。我一直安慰自己,他們只是感情好待诅,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布叹坦。 她就那樣靜靜地躺著,像睡著了一般卑雁。 火紅的嫁衣襯著肌膚如雪募书。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天测蹲,我揣著相機與錄音莹捡,去河邊找鬼。 笑死扣甲,一個胖子當(dāng)著我的面吹牛篮赢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播文捶,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼荷逞,長吁一口氣:“原來是場噩夢啊……” “哼媒咳!你這毒婦竟也來了粹排?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤涩澡,失蹤者是張志新(化名)和其女友劉穎顽耳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妙同,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡射富,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了粥帚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胰耗。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖芒涡,靈堂內(nèi)的尸體忽然破棺而出柴灯,到底是詐尸還是另有隱情卖漫,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布赠群,位于F島的核電站羊始,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏查描。R本人自食惡果不足惜突委,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冬三。 院中可真熱鬧匀油,春花似錦、人聲如沸长豁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匠襟。三九已至钝侠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酸舍,已是汗流浹背帅韧。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留啃勉,地道東北人忽舟。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像淮阐,于是被迫代替她去往敵國和親叮阅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359