PHP进阶学习之Geo的地图定位算法详解

 更新时间:2019年8月20日 21:19  点击:376

本文实例讲述了PHP进阶学习之Geo的地图定位算法。分享给大家供大家参考,具体如下:

前言

日常开发中我们经常需要查找某个物体的定位,或者查找附近的范围等,我们自然而然会想到的方法就是利用各种提供服务的地图网站的API,基于API,用经纬度去实现定位和查找附近范围等等。然而,由于原理没有做一个了解和一定的认识,在对比距离远近关系或者控制精确程度方面,我们并不了解怎么利用这些经纬度数值去实现距离转化和对比。本章节我们就来探讨一下基于geo的位置算法原理。

概念

  1. 纬线:纬线是与地轴垂直的线,着东西方向环绕地球一周,所有的纬度都是平行的。其中,赤道是最长的纬线,纬度为0度,纬线数值是角度数值,从赤道开始分为北纬和南纬,都是0-90°;

  2. 经线:地球仪上的竖线,是连接南北两极并且与纬线垂直相交的半圆,子午线为0°,分为西经和东经,都是0-180°,经线也是角度数值;

  3. 经纬线和米的换算:经度或者纬度0.00001度,约等于1米,这个在GPS测算距离的时候可以体会到,GPS只要精确到小数点后五位,就是10米范围内的精度;

  4. 为了便于理解,将地球看成一个基于经纬度线的坐标系。经度范围为-180~180°,纬度范围为-90~90°,地球上任意一点都可以用经纬度这样两个维度去唯一确定

在实际应用中,如果要用两个维度去确定一个点,则计算量会很大,因为一个二维确定一个平面,如果我们把二维平面上的所有点都用一个数字表示,即经纬度换算成一个字符串,则可以转为一维坐标来表示,大大减少计算量。这就是现在应用广泛的geoHash。

geoHash:Geohash是公共领域地理编码系统,它将地理位置编码为一串字母和数字。Geohash提供了像任意精度这样的属性,以及逐渐从代码末尾删除字符以减小其大小(并逐渐失去精度)的可能性。由于逐步精度下降的结果,附近的地方往往(但不总是)呈现类似的前缀。共享前缀越长,两个地方越接近。

原理

能将一个地球上的点表示成一串字母,并且相近的地点字母的共同前缀越多。这能让位置搜索在开发中变得很容易。它的原理就是依据上述说的geoHash值。下面就来详细说明geoHash值是怎么算出来的:

  1. 根据经纬度计算GeoHash二进制编码(以经纬度值:(116.389550,39.928167)进行算法说明)

  2. 先计算纬度二进制:
     2.1 区间[-90,90]进行二分为[-90,0),[0,90],称为左右区间,可以确定39.928167属于右区间[0,90],给标记为1;
     2.2 接着将区间[0,90]进行二分为 [0,45),[45,90],可以确定39.928167属于左区间 [0,45),给标记为0;
     2.3 递归上述过程39.928167总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,并越来越逼近39.928167
     2.4 这样随着算法的进行会产生一个序列1011100011的纬度二进制编码;

  1. 同理,计算出地球经度二进制,区间是[-180,180],可以对经度116.389550进行编码。算出结果1101001011;


  1. 组码:通过上述计算,纬度产生的编码为10111 00011,经度产生的编码为11010 01011。偶数位放经度,奇数位放纬度,把2串编码组合生成新串:11100 11101 00100 01111。

  2. 使用用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,首先将11100 11101 00100 01111转成十进制,对应着28、29、4、15,十进制对应的编码就是wx4g

Geohash其实就是将整个地图或者某个分割所得的区域进行一次划分,由于采用的是base32编码方式,即Geohash中的每一个字母或者数字(如wx4g0e中的w)都是由5bits组成(2^5 = 32,base32),这5bits可以有32中不同的组合(0~31),这样我们可以将整个地图区域分为32个区域,通过00000 ~ 11111来标识这32个区域。第一次对地图划分后的情况如下图所示(每个区域中的编号对应于该区域所对应的编码):
进行多次分解后,我们就可以得到更精确的位置划分,如上述计算的wx4g已经可以精确到一个城市城区了:
从上图中可以看出,相邻城区的geoHash值的共同前缀越多,由此我们就可以应用共同前缀来判断附近相邻的位置了。当然精确范围也是根据经纬度和hash值的范围来确定的,如下表,geo精确到8位的共同前缀,可以表示附近约20米内的范围了

在PHP中的实现与应用

在了解了geo的位置算法原理后,PHP开发过程中我们便可以使用这一定位功能,目前解决位置定位和搜索功能的方案有很多种,基于PHP的,从本人自身实践中推荐一下几种:

  1. 利用现成的地图API实现geo定位、搜索范围、计算距离等功能,如国内的百度、高德等,很多免费API可以使用;如需更大更精确的范围,可以使用google的geo api,缺点就是每日请求次数有限制,如果是企业级别的应用,付费增加请求次数的允许权限是必不可少的。可查阅链接:https://developers.google.com/maps/documentation/geocoding/start

  2. 通过NoSQL存储组件实现定位运算和存储:由于我们经常在计算了定位数据之后要把数据落地,所以目前行业内已经有了很多存储组件提供了直接计算和存储的方案,如MongoDB,适合在国内云平台直接使用。如果是AWS平台,也提供了dynamodb这种NoSQL存储组件。这些存储组件均可以直接传入经纬度,自动换算为geoHash落地存储,也提供了直接计算距离,搜索范围数据返回的功能。

  3. 本地部署服务器可用Redis在Redis3.2版本之后,已经提供了GEO的运算、搜索和落地功能,可以结合新版本的php-redis扩展实现geo的方法。参考链接:http://www.redis.cn/commands.html,在PHP中实现对redis的geo操作,可以参考GitHub上面已经提供了的方法说明:https://github.com/phpredis/phpredisredis其实是封装了方法计算经纬度参数,换算成geohash值作为Zset的Score存入Zset中,所以也可以将其当做一个普通的Zset进行操作
     实际应用中我们常常以商品、人物作为value值,以geohash值作为score,这样就可以搜索一定范围内score内的人或事物了。如搜索一定半径内的value:

    $redis->geoRadius($key, $longitude, $latitude, $radius, $unit [, Array $options]);

     

  1. 利用PHP进行原生geoHash计算:这种方式计算较为复杂,即是根据geoHash原理,用PHP语言实现了这一算法,也通过PHP计算距离,搜索半径等。相当于重新造了个轮子,当然如果业务复杂度较高,也有必要进行PHP对GeoHash算法的支持,或者自行封装Geo类。在此推荐GitHub上面一个比较完善的PHP-GEO支持:https://github.com/geocoder-php/Geocoder
     或者如果只需要计算GeoHash值,可以使用网上广泛转发的一个计算Hash值的PHP方法:

private $coding = '0123456789bcdefghjkmnpqrstuvwxyz';/*** calculate geoHash by longitude and latitude* @param $lat* @param $long* @return string*/public function calcGeoHash($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;}/*** @param $number* @return float|int*/private function precision($number){$precision=0;$pt=strpos($number,'.');if ($pt!==false){$precision=-(strlen($number)-$pt-1);}return pow(10,$precision)/2;}/*** @param $number* @param $min* @param $max* @param $bitcount* @return string*/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);elsereturn '0'.$this->binEncode($number, $min, $mid,$bitcount-1);}

总结

GeoHash算法是一种将二维坐标换算成一位字符串的算法,可以通过不同字符串的共同前缀来判断相距远近。在日常业务中也常常需要用到,本文也介绍了不同的实现方法,具体实现方案还需以实际业务需要为准。如果属于精确度要求很高或者企业级的大规模应用,可以首先考虑MongoDB或者其他提供Geo功能的存储组件,如果较为轻量级,可以借助第三方地区API、或者利用redis做geo的简单应用。如果业务需求复杂度不高,在这里并不推荐直接使用PHP写,毕竟效率会比较低,而且这也不是业务关注的重点,所以没必要重新造轮子


希望本文所述对大家PHP程序设计有所帮助。

[!--infotagslink--]

相关文章

  • mysql_connect与mysql_pconnect的区别详解

    在mysql中我们会看到有两种常用的数据库连接模式,一种是长久连接,另一各是页面访问完之后就断了连接,下面我来分别介绍mysql_connect与mysql_pconnect的区别,有需要了解...2016-11-25
  • phpems SQL注入(cookies)分析研究

    PHPEMS(PHP Exam Management System)在线模拟考试系统基于PHP+Mysql开发,主要用于搭建模拟考试平台,支持多种题型和展现方式,是国内首款支持题冒题和自动评分与教师评分相...2016-11-25
  • PHP添加MongoDB扩展实例教程

    由于要使用mikoomi mongodb plugin插件,所以需要php对mongodb的扩展支持,默认通过源安装的php并没有mongodb的扩展支持,具体可以通过php -m|grep mongo 验证 。这里就结...2016-11-25
  • php身份证校验码的计算例子

    下面来给各位同学介绍一个php身份证校验码的计算例子,希望本函数代码能帮助到各位同学哦。 例子 代码如下 复制代码 public function id_ver...2016-11-25
  • PHP session_start()很慢问题分析与解决办法

    本文章来给各位同学介绍一下关于PHP session_start()很慢问题分析与解决办法,希望碰到此问题的同学可进入参考。 最近在做东西的时候发现一个问题 有一个接口挂...2016-11-25
  • php生成二维码中文乱码问题解决方法

    最近做了个扫描二维码得到vcard的项目,遇到一个问题,有一部分生成完的二维码,用android系统手机扫描后得到的vcard中的中文姓名是乱码,经过比对发现,这部分vcard中ORG这个...2016-11-25
  • php读取zip文件(删除文件,提取文件,增加文件)实例

    下面小编来给大家演示几个php操作zip文件的实例,我们可以读取zip包中指定文件与删除zip包中指定文件,下面来给大这介绍一下。 从zip压缩文件中提取文件 代...2016-11-25
  • php中网页添加到桌面快捷方式方法

    我们经常会在网站中看到可以直接把网站以快捷方式保存到自己的电脑中,然后只要点击就可以实现进入网了,那么php中怎么把网页添加到桌面快捷方式呢。 功能简单,直接上...2016-11-25
  • php5.3下使用php管理crontab计划任务

    php5.3或以上版本可以使用php管理crontab计划任务,下面我先来体验一下,有需要学习了解的朋友可进入参考。 1.使用php-crontab-manager管理计划任务 要求 PHP>=5.3...2016-11-25
  • ASP/PHP sql注入语句整理大全

    SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作 标准注入语句1.判...2016-11-25
  • php正确禁用eval函数与误区介绍

    eval函数在php中是一个函数并不是系统组件函数,我们在php.ini中的disable_functions是无法禁止它的,因这他不是一个php_function哦。 eval()针对php安全来说具有很...2016-11-25
  • php svn操作类

    以前我们开发大型项目时都会用到svn来同步,因为开发产品的人过多,所以我们会利用软件来管理,今天发有一居然可以利用php来管理svn哦,好了看看吧。 代码如下 ...2016-11-25
  • php中json_decode()和json_encode()用法与中文不显示解决办法

    本文章介绍了关于php中json_decode()和json_encode()用法与中文不显示解决办法,有需要的朋友可以参考一下下。 php中json_decode()和json_encode() 1.json_decode(...2016-11-25
  • php中eval()函数操作数组的方法

    在php中eval是一个函数并且不能直接禁用了,但eval函数又相当的危险了经常会出现一些问题了,今天我们就一起来看看eval函数对数组的操作 例子, <?php $data="array...2016-11-25
  • phpexcel导出数据身份证后四位0000解决办法

    在php中我们如果要导入excel数据我们通常会使用phpexcel插件了,但是有朋友会发与使用phpexcel导出数据出现身份证后四位是0000情况了,下面我们就来看解决办法。 最...2016-11-25
  • PHP 数据库缓存Memcache操作类

    操作类就是把一些常用的一系列的数据库或相关操作写在一个类中,这样调用时我们只要调用类文件,如果要执行相关操作就直接调用类文件中的方法函数就可以实现了,下面整理了...2016-11-25
  • PHP7快速编译安装的步骤

    编译安装非常的简单了我们现在的php版本已经到了php7了,下文小编来为各位介绍一篇关于PHP7快速编译安装的步骤,希望文章能够帮助到各位。 一、安装必要一些依赖 yum...2016-11-25
  • php KindEditor文章内分页的实例方法

    我们这里介绍php与KindEditor编辑器使用时如何利用KindEditor编辑器的分页功能实现文章内容分页,KindEditor编辑器在我们点击分页时会插入代码,我们只要以它为分切符,就...2016-11-25
  • PHP成员变量获取对比(类成员变量)

    下面本文章来给大家介绍在php中成员变量的一些对比了,文章举了四个例子在这例子中分别对不同成员变量进行测试与获取操作,下面一起来看看。 有如下4个代码示例,你认...2016-11-25
  • php 中file_get_contents超时问题的解决方法

    file_get_contents超时我知道最多的原因就是你机器访问远程机器过慢,导致php脚本超时了,但也有其它很多原因,下面我来总结file_get_contents超时问题的解决方法总结。...2016-11-25