查找附近点--Geohash方案讨论

本文介绍了一种利用地理哈希编码实现高效附近搜索的方法,通过减少数据库查询次数,提高性能并提供缓存功能。详细阐述了编码算法、PHP实现、策略对比以及SQL查询优化,适用于定位服务、地图应用等领域。



基础数据中,一般保存了目标位置的经纬度;利用用户提供的经纬度,进行对比,从而获得是否在附近。

查找附近的XXX,由近到远返回结果,且结果中有与目标点的距离。

一、方案A:
=================================================================================================

点(纬度,经度),A($radLat1,$radLng1)、B($radLat2,$radLng2);

缺点:每次都会查询数据库,性能堪忧

通过余弦定理以及弧度计算方法,最终推导出来的算式A为:


$s = acos(cos($radLat1)*cos($radLat2)*cos($radLng1-$radLng2)+sin($radLat1)*sin($radLat2))*$R;




$s = 2*asin(sqrt(pow(sin(($radLat1-$radLat2)/2),2)+cos($radLat1)*cos($radLat2)*pow(sin(($radLng1-$radLng2)/2),2)))*$R;

其中 :
$radLat1、$radLng1,$radLat2,$radLng2 为弧度

2、通过测试两种算法,结果相同且都正确,但通过PHP代码测试,两点间距离,10W次性能对比,自行推导版本计算时长算式B较优,如下:

0.56368780136108float(431)
0.57460689544678float(431)
0.59051203727722float(431)

0.47404885292053float(431)
0.47808718681335float(431)
0.47946381568909float(431)




 
    //根据经纬度计算距离 其中A($lat1,$lng1)、B($lat2,$lng2)
    public static function getDistance($lat1,$lng1,$lat2,$lng2)
    {
        //地球半径
        $R = 6378137;
 
        //将角度转为狐度
        $radLat1 = deg2rad($lat1);
        $radLat2 = deg2rad($lat2);
        $radLng1 = deg2rad($lng1);
        $radLng2 = deg2rad($lng2);
 
        //结果
        $s = acos(cos($radLat1)*cos($radLat2)*cos($radLng1-$radLng2)+sin($radLat1)*sin($radLat2))*$R;
 
        //精度
        $s = round($s* 10000)/10000;
 
        return  round($s);
    }
 
?>


4、在实际应用中,需要从数据库中遍历取出符合条件,以及排序等操作,

4.1、创建Mysql存储函数,并对经纬度字段建立索引



 
    //根据经纬度计算距离 其中A($lat1,$lng1)、B($lat2,$lng2)
    public static function getDistance($lat1,$lng1,$lat2,$lng2)
    {
        //地球半径
        $R = 6378137;
 
        //将角度转为狐度
        $radLat1 = deg2rad($lat1);
        $radLat2 = deg2rad($lat2);
        $radLng1 = deg2rad($lng1);
        $radLng2 = deg2rad($lng2);
 
        //结果
        $s = acos(cos($radLat1)*cos($radLat2)*cos($radLng1-$radLng2)+sin($radLat1)*sin($radLat2))*$R;
 
        //精度
        $s = round($s* 10000)/10000;
 
        return  round($s);
    }
 
?>



通过SQL,可设置距离以及排序;可搜索出符合条件的信息,以及有一个较好的排序

1
SELECT *,latitude,longitude,GETDISTANCE(latitude,longitude,30.663262,104.071619) AS distance FROM  mb_shop_ext where 1 HAVING distance


=================================================================================================

比如,成都永丰立交的编码是wm3yr31d2524

1、利用一个字段,即可存储经纬度;搜索时,只需一条索引,效率较高
2、编码的前缀可以表示更大的区域,查找附近的,非常方便。 SQL中,LIKE ‘wm3yr3%’,即可查询附近的所有地点。
3、通过编码精度可模糊坐标、隐私保护等。

1、geohash的编码算法

1.1、纬度范围(-90, 90)平分成两个区间(-90, 0)、(0, 90),如果目标纬度位于前一个区间,则编码为0,否则编码为1。
由于30.625265属于(0, 90),所以取编码为1。
然后再将(0, 90)分成 (0, 45), (45, 90)两个区间,而39.92324位于(0, 45),所以编码为0,
然后再将(0, 45)分成 (0, 22.5), (22.5, 45)两个区间,而39.92324位于(22.5, 45),所以编码为1,
依次类推可得永丰立交纬度编码为101010111001001000100101101010。

1.3、合并经纬度编码,从高到低,先取一位经度,再取一位纬度;得出结果 111001001100011111101011100011000010110000010001010001000100












11100 10011 00011 11110 10111 00011 00001 01100 00010 00101 00010 00100 => wm3yr31d2524
 
十进制  0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15
base32   0   1   2   3   4   5   6   7   8   9   b   c   d   e   f   g
十进制  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31
base32   h   j   k   m   n   p   q   r   s   t   u   v   w   x   y   z


2、策略

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

3、PHP基类




 

 
class Geohash
{
    private $coding="0123456789bcdefghjkmnpqrstuvwxyz";
    private $codingMap=array();
 
    public function Geohash()
    {
        for($i=0; $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
        {
            $binary.=$this->codingMap[substr($hash,$i,1)];
        }
 
        $bl=strlen($binary);
        $blat="";
        $blong="";
        for ($i=0; $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
        {
            $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);
    }
}
 
?>


三、测试



 
require_once('Mysql.class.php');
require_once('geohash.class.php');
 
//mysql
$conf = array(
 
    'host' => '127.0.0.1',
    'port' => 3306,
    'user' => 'root',
    'password' => '123456',
    'database' => 'mocube',
    'charset' => 'utf8',
    'persistent' => false
);
 
$mysql = new Db_Mysql($conf);
$geohash=new Geohash;
 
//经纬度转换成Geohash

 
//获取附近的信息
$n_latitude = $_GET['la'];
$n_longitude = $_GET['lo'];
 
//开始
$b_time = microtime(true);
 
//方案A,直接利用数据库存储函数,遍历排序

 
//方案B geohash求出附近,然后排序
 
//当前 geohash值
$n_geohash = $geohash->encode($n_latitude,$n_longitude);
 
//附近
$n = $_GET['n'];
$like_geohash = substr($n_geohash, 0, $n);
 
$sql = 'select * from mb_shop_ext where geohash like "'.$like_geohash.'%"';
 
echo $sql;
 
$data = $mysql->queryAll($sql);
 
//算出实际距离
foreach($data as $key=>$val)
{
    $distance = getDistance($n_latitude,$n_longitude,$val['latitude'],$val['longitude']);
 
    $data[$key]['distance'] = $distance;
 
    //排序列
    $sortdistance[$key] = $distance;
}
 
//距离排序
array_multisort($sortdistance,SORT_ASC,$data);
 
//结束
$e_time = microtime(true);
 
echo $e_time - $b_time;
 
var_dump($data);
 
//根据经纬度计算距离 其中A($lat1,$lng1)、B($lat2,$lng2)
function getDistance($lat1,$lng1,$lat2,$lng2)
{
    //地球半径
    $R = 6378137;
 
    //将角度转为狐度
    $radLat1 = deg2rad($lat1);
    $radLat2 = deg2rad($lat2);
    $radLng1 = deg2rad($lng1);
    $radLng2 = deg2rad($lng2);
 
    //结果
    $s = acos(cos($radLat1)*cos($radLat2)*cos($radLng1-$radLng2)+sin($radLat1)*sin($radLat2))*$R;
 
    //精度
    $s = round($s* 10000)/10000;
 
    return  round($s);
}
 
?>



方案B的亮点在于:
1、搜索结果可缓存,重复使用,不会因为用户有小范围的移动,直接穿透数据库查询。
2、先缩小结果范围,再运算、排序,可提升性能。

在实际应用场景中,方案B数据库搜索可内存缓存;且如数据量更大,方案B结果会更优。

0.016560077667236
0.032402992248535
0.040318012237549

0.0079810619354248
0.0079669952392578
0.0064868927001953

两种方案,根据应用场景以及负载情况合理选择,当然推荐方案B;
不管哪种方案,都记得,给列加上索引,利于数据库检索。

 

<think> 首先,用户的问题是:“我想了解HASH算法相关信息 请问HASH算法介绍”。根据系统指令,我需要用中文回答,结构清晰,逐步解决问题,并保证真实可靠。参考提供的引用内容,这些是关于哈希算法的原理、特、应用等。 关键引用总结: - 引用[1]:介绍哈希算法原理,包括什么是Hash、Hash的特、Hash碰撞解决方案(链地址法、开放地址法)、应用(信息加密、数据校验、负载均衡)以及扩展应用(SimHash、GeoHash、布隆过滤器)。 - 引用[2]:讨论哈希在HashMap中的实现,强调容量为什么是2的n次幂,使用位运算提高效率。 - 引用[3]:描述优秀哈希算法的特性:正向快速、逆向困难、输入敏感、冲突避免。 - 引用[4]:简要解释Java中的hashCode方法作为对象的哈希码。 用户的问题是基于这些引用介绍哈希算法,所以我应该整合这些信息,构建一个全面的介绍。 系统指令要求: - 行内数学表达式用$...$格式。 - 独立公式用$$...$$格式,单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题(在末尾)。 - 回答中引用的段落末尾添加引用标识,如[^1]。 回答结构: 1. **开头**:简要介绍哈希算法是什么。 2. **主体**: - 定义哈希算法。 - 哈希的特- 哈希碰撞及解决方案- 哈希算法的应用。 - 扩展应用(可选)。 - 优秀哈希算法的特性。 - 在编程中的实现(如Java的hashCode)。 3. **相关问题**:在末尾生成几个相关问题,以“§§相关问题§§”开头。 确保真实可靠: - 基于引用和自己的知识构建回答。哈希算法是计算机科学基础,可以依赖标准知识。 - 引用内容时,自然地添加引用标识。 数学表达式: - 哈希函数可以用数学公式表示。例如,哈希函数 $h(x)$ 将输入映射到固定大小输出。 - 碰撞解决方案如开放地址法的公式可以用LaTeX。 可能的LaTeX: - 定义:$h: \text{input} \rightarrow \{0, 1\}^n$(输出n位哈希值)。 - 开放地址法:$h_i(x) = (h(x) + i) \mod m$ for probe sequence. - 但这可能需要简化以适应介绍水平。 步骤: 1. **什么是哈希算法?** - 哈希算法将任意长度的输入(明文)映射到固定长度的输出(哈希值)。 - 引用[1]和[3]。 - 例如,哈希函数 $h(x)$ 产生一个摘要。 2. **哈希的特:** - 正向快速、逆向困难、输入敏感、冲突避免(引用[3])。 3. **哈希碰撞:** - 碰撞是指两个不同输入产生相同哈希值。 - 解决方案:链地址法(使用链表)、开放地址法(线性探测、二次探测)。 - 提供简单demo或描述(引用[1])。 4. **应用:** - 信息加密(如密码存储)、数据校验(如文件完整性)、负载均衡(如分布式系统)。 - 扩展:SimHash(相似性检测)、GeoHash(地理位置)、布隆过滤器(成员测试)。 5. **在编程中的实现:** - 如Java的hashCode()方法(引用[4])。 - HashMap实现细节:容量为2^n,使用位运算 $(n-1) \& hash$ 提高效率(引用[2])。 6. **总结:** 哈希算法的重要性。 相关问题: - 基于引用和主题,生成2-4个相关问题。 - 例如: 1. 如何选择合适的哈希函数? 2. 哈希碰撞对性能的影响? 3. 布隆过滤器的工作原理? 4. 为什么HashMap容量是2的幂? 最终回答: - 用中文。 - 结尾添加“§§相关问题§§”列表。 </think> ### 哈希算法介绍 哈希算法(Hash Algorithm)是一种广泛应用于计算机科学和密码学中的数学函数,它将任意长度的输入数据(称为“明文”)映射到一个固定长度的输出值(称为“哈希值”或“摘要”)。哈希值的长度通常是固定的(如128位、256位),无论输入大小如何。这种算法在数据完整性验证、加密存储和高效查找等领域发挥着核心作用。下面我将基于参考引用,逐步介绍哈希算法的核心原理、特、碰撞解决方案及应用。 #### 1. **什么是哈希算法?** - 哈希算法本质上是一个单向函数 $h(x)$,其中输入 $x$ 可以是任意数据(如字符串、文件),输出是一个固定大小的二进制序列(例如SHA-256算法输出256位哈希值)。这个过程称为“哈希计算”。 - 核心目标是将输入压缩成一个独特的标识符,使得不同输入尽可能产生不同输出(理想情况下唯一)。例如,输入 "hello" 通过哈希函数可能输出类似 `2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824` 的值[^1]。 - 数学表示为:$h: \text{Input} \rightarrow \{0,1\}^n$,其中 $n$ 是输出长度。例如,在MD5算法中 $n=128$[^3]。 #### 2. **哈希算法的特** 一个优秀的哈希算法应满足以下特性(基于引用[^3]): - **正向快速**:给定明文和算法,能在有限时间内高效计算出哈希值(时间复杂度通常为 $O(1)$ 或 $O(n)$)。 - **逆向困难**:给定哈希值,无法在可行时间内反推原始输入(单向性),这在加密中保护数据安全。 - **输入敏感**:输入微小变化(如修改一个字符)会导致哈希值显著不同(雪崩效应)。例如,输入 "hello" 和 "hellp" 的哈希值差异巨大。 - **冲突避免**:很难找到两个不同输入 $x_1$ 和 $x_2$ 使得 $h(x_1) = h(x_2)$(称为“哈希碰撞”)。理想哈希函数应使碰撞概率极低。 #### 3. **哈希碰撞及其解决方案** 尽管算法设计避免碰撞,但碰撞仍可能发生(例如生日攻击)。常见的解决方案包括: - **链地址法**:使用链表存储冲突元素。在哈希表中,每个桶(bucket)维护一个链表,当多个输入映射到同一桶时,元素被添加到链表尾部。这确保了 $O(1)$ 的平均查找时间[^1]。 - **开放地址法**:当碰撞发生时,探测新的位置。常用方法包括线性探测($h_i(x) = (h(x) + i) \mod m$)和二次探测($h_i(x) = (h(x) + i^2) \mod m$),其中 $i$ 是探测步数,$m$ 是表大小[^1]。 - 两种方案各有优劣:链地址法简单但可能链表过长;开放地址法节省空间但需处理聚集问题。实际选择取决于应用场景。 #### 4. **哈希算法的应用** 哈希算法在日常和工业场景中无处不在: - **信息加密**:用于安全存储密码(如bcrypt算法),避免明文泄露。输入密码通过哈希函数生成摘要,数据库仅存储摘要而非原始密码[^1]。 - **数据校验**:验证文件完整性(如下载文件校验MD5值)。微小的数据改动会导致哈希值变化,确保数据未被篡改[^1]。 - **负载均衡**:在分布式系统中(如Web服务器),哈希算法将请求均匀分发到不同节(例如,基于用户IP计算哈希值分配服务器)[^1]。 - **编程实现**:在Java中,`hashCode()` 方法基于对象内容生成哈希码(一个整数),用于快速定位对象在数据结构(如HashMap)中的位置[^4]。HashMap优化中,容量设为2的幂(如16、32),并使用位运算 $(n-1) \& hash$ 替代取模运算,提高效率并减少碰撞[^2]。 #### 5. **扩展应用** 哈希算法衍生出高级变体以满足特定需求: - **SimHash**:用于文本相似度检测(如搜索引擎去重),通过局部敏感哈希计算相似文档的近似哈希值[^1]。 - **GeoHash**:将地理位置编码为字符串,便于空间索引(如地图服务)[^1]。 - **布隆过滤器**:概率型数据结构,用于高效成员测试(如检查元素是否在集合中),它使用多个哈希函数减少碰撞风险[^1]。 #### 6. **总结** 哈希算法是现代计算的基础工具,其核心价值在于高效性、安全性和唯一性。它确保了数据处理的可靠性(如区块链中的Merkle树),同时通过优化(如HashMap的位运算)提升了系统性能。然而,算法选择需权衡碰撞概率和计算开销(例如SHA-3比MD5更安全)。理解哈希原理有助于设计更健壮的系统[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值