php利用geohash算法查找附近的人

之前没做过,后来自己写好了发现个问题,读出来的就不是附近的人。只是单纯的计算出两点间的距离而已,后来又想了一下,并且网上找了找,对比了一下。用geohash 算法 还是比较不错的。

下面附上方法步骤

原理

首先要了解一下  geohash 算法  网上关于这个讲的蛮多的,在此不详细说明了,我简单的理解就是无限缩小定位一个范围。虽说是无限的,但是还是有限的。

比如一个坐标点(29,121) ,把经纬度分为 (-90,0) (0,90)  (-180,0)(0,180)  ,如果给的坐标在这个区间内,就为1 不在的话 就为 0 ,以纬度然后继续分  在(0,45)的区间内 则为1 不在则为0,然后继续  (0,22.5)(22.5,45)一直分直到达到你所需要的精度为止。

然后得到类似 1011 1000 1100 0111 1001 这样的字符串   经度的 也是一样 合并两个字符串  11100 11101 00100 01111 00000 01101 01011 00001   奇数位为 纬度   偶数位  为经度。最后,用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码。  然后得到的 类似   wx4g0ec1(举例,不准的) 这样的字符串。

附 PHPGeohash.class.php   基类

<?php
class Geohash
{
    private $coding="0123456789bcdefghjkmnpqrstuvwxyz";
    private $codingMap=array();
    public function Geohash()
    {
        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="";
        $blong="";
        for ($i=0; $i<$bl; $i++)
        {
            if ($i%2)
                $blat=$blat.substr($binary,$i,1);
            else
                $blong=$blong.substr($binary,$i,1);
  
        }
  
        $lat=$this->binDecode($blat,-90,90);
        $long=$this->binDecode($blong,-180,180);
  
        $latErr=$this->calcError(strlen($blat),-90,90);
        $longErr=$this->calcError(strlen($blong),-180,180);
  
        $latPlaces=max(1, -round(log10($latErr))) - 1;
        $longPlaces=max(1, -round(log10($longErr))) - 1;
  
        $lat=round($lat, $latPlaces);
        $long=round($long, $longPlaces);
  
        return array($lat,$long);
    }
  
    public function encode($lat,$long)
    {
        $plat=$this->precision($lat);
        $latbits=1;
        $err=45;
        while($err>$plat)
        {
            $latbits++;
            $err/=2;
        }
  
        $plong=$this->precision($long);
        $longbits=1;
        $err=90;
        while($err>$plong)
        {
            $longbits++;
            $err/=2;
        }
  
        $bits=max($latbits,$longbits);
  
        $longbits=$bits;
        $latbits=$bits;
        $addlong=1;
        while (($longbits+$latbits)%5 != 0)
        {
            $longbits+=$addlong;
            $latbits+=!$addlong;
            $addlong=!$addlong;
        }
  
        $blat=$this->binEncode($lat,-90,90, $latbits);
  
        $blong=$this->binEncode($long,-180,180,$longbits);
  
        $binary="";
        $uselong=1;
        while (strlen($blat)+strlen($blong))
        {
            if ($uselong)
            {
                $binary=$binary.substr($blong,0,1);
                $blong=substr($blong,1);
            }
            else
            {
                $binary=$binary.substr($blat,0,1);
                $blat=substr($blat,1);
            }
            $uselong=!$uselong;
        }
  
        $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);
    }
?>

查找

把这样的一条数据(wx4g0ec1)存入到你的数据表里面

把你需要查找的坐标  转换成  类似(wx4g0ec1)字符  ,然后用LIKE  查询 你存入的 坐标信息

 

查找附近,利用 在SQL中 LIKE ‘wx4g%’;且此结果可缓存;在小区域内,不会因为改变经纬度,而重新数据库查询

查找出的有限结果,如需要求距离或者排序,可利用距离公式和二维数据排序;此时也是少量数据,会很快的

查出来的数据按照距离远近排序一下,然后对查出的数据分下页就好了

测算距离的函数。(具体原理不在详细说明)

/**
     * 说明: 根据两点间的经纬度计算距离
     * @param type $lat1 纬度值1
     * @param type $lng1 经度值1
     * @param type $lat2 纬度值2
     * @param type $lng2 经度值2
     * @return type
     */
    function getDistance($lat1, $lng1, $lat2, $lng2) {
        $earthRadius = 6367000;
        $lat1 = ($lat1 * pi() ) / 180;
        $lng1 = ($lng1 * pi() ) / 180;
        $lat2 = ($lat2 * pi() ) / 180;
        $lng2 = ($lng2 * pi() ) / 180;
        $calcLongitude = $lng2 - $lng1;
        $calcLatitude = $lat2 - $lat1;
        $stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2);
        $stepTwo = 2 * asin(min(1, sqrt($stepOne)));
        $calculatedDistance = $earthRadius * $stepTwo;
        return $calculatedDistance;
    }
排序函数
    php 自带的 函数 array_multisort具体怎么用可以查下手册;
下面是我写的一个  原先不知道有自带函数。。
  /**
     * 对二维数组进行排序
     * @param type $arr
     * @param type $keys
     * @param type $type
     * @return type
     */
    function array_sort($arr, $keys, $type = 'asc') {
        $keysvalue = $new_array = array();
        foreach ($arr as $k => $v) {
            $keysvalue[$k] = $v[$keys];
        }
        if ($type == 'asc') {
            asort($keysvalue);
        } else {
            arsort($keysvalue);
        }
        foreach ($keysvalue as $k => $v) {
            $new_array[$k] = $arr[$k];
        }
        $array = array();
        foreach ($new_array as $v) {
            $array[] = $v;
        }
        return $array;
    }

发表评论

电子邮件地址不会被公开。 必填项已用*标注