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


Posted in PHP onJune 19, 2019

本文实例讲述了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的纬度二进制编码;PHP进阶学习之Geo的地图定位算法详解
  1. 同理,计算出地球经度二进制,区间是[-180,180],可以对经度116.389550进行编码。算出结果1101001011;

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

  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个区域。第一次对地图划分后的情况如下图所示(每个区域中的编号对应于该区域所对应的编码):PHP进阶学习之Geo的地图定位算法详解
进行多次分解后,我们就可以得到更精确的位置划分,如上述计算的wx4g已经可以精确到一个城市城区了:PHP进阶学习之Geo的地图定位算法详解
从上图中可以看出,相邻城区的geoHash值的共同前缀越多,由此我们就可以应用共同前缀来判断附近相邻的位置了。当然精确范围也是根据经纬度和hash值的范围来确定的,如下表,geo精确到8位的共同前缀,可以表示附近约20米内的范围了PHP进阶学习之Geo的地图定位算法详解

在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/phpredis。redis其实是封装了方法计算经纬度参数,换算成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);
else
return '0'.$this->binEncode($number, $min, $mid,$bitcount-1);
}

总结

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

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

PHP 相关文章推荐
在线竞拍系统的PHP实现框架(二)
Oct 09 PHP
PHP 遍历XP文件夹下所有文件
Nov 27 PHP
PHP+XML 制作简单的留言本 图文教程
Nov 02 PHP
PHP和Mysqlweb应用开发核心技术 第1部分 Php基础-3 代码组织和重用2
Jul 03 PHP
PHP把空格、换行符、中文逗号等替换成英文逗号的正则表达式
May 04 PHP
PHP数字和字符串ID互转函数(类似优酷ID)
Jun 30 PHP
PHP实现文件下载断点续传详解
Oct 15 PHP
WordPress自定义时间显示格式
Mar 27 PHP
php实现当前页面点击下载文件的实例代码
Nov 16 PHP
Mac系统下安装PHP Xdebug
Mar 30 PHP
php实现的顺序线性表示例
May 04 PHP
php文件包含的几种方式总结
Sep 19 PHP
PHP进阶学习之依赖注入与Ioc容器详解
Jun 19 #PHP
yii2 在控制器中验证请求参数的使用方法
Jun 19 #PHP
php自定义排序uasort函数示例【二维数组按指定键值排序】
Jun 19 #PHP
windows 2008r2+php5.6.28环境搭建详细过程
Jun 18 #PHP
PHP进阶学习之类的自动加载机制原理分析
Jun 18 #PHP
PHP进阶学习之垃圾回收机制详解
Jun 18 #PHP
PHP进阶学习之命名空间基本用法分析
Jun 18 #PHP
You might like
PHP XML error parsing SOAP payload on line 1
2010/06/17 PHP
PHP利用header跳转失效的解决方法
2014/10/24 PHP
浅析php原型模式
2014/11/25 PHP
Laravel手动分页实现方法详解
2016/10/09 PHP
PHP魔术方法以及关于独立实例与相连实例的全面讲解
2016/10/18 PHP
详解laravel安装使用Passport(Api认证)
2018/07/27 PHP
javascript 处理事件绑定的一些兼容写法
2009/12/24 Javascript
js监听表单value的修改同步问题,跨浏览器支持
2009/12/31 Javascript
js判断设备是否为PC并调整图片大小
2014/02/12 Javascript
jquery mobile的触控点击事件会多次触发问题的解决方法
2014/05/08 Javascript
jQuery+css实现百度百科的页面导航效果
2014/12/16 Javascript
详细分析使用AngularJS编程中提交表单的方式
2015/06/19 Javascript
使用postMesssage()实现跨域iframe页面间的信息传递方法
2016/03/29 Javascript
基于javascript实现简单的抽奖系统
2020/04/15 Javascript
BootstrapValidator不触发校验的实现代码
2016/09/28 Javascript
BootStrap Fileinput的使用教程
2016/12/30 Javascript
JavaScript 自定义事件之我见
2017/09/25 Javascript
学习使用ExpressJS 4.0中的新Router的用法
2018/11/06 Javascript
JavaScript中构造函数与原型链之间的关系详解
2019/02/25 Javascript
详解bootstrap-fileinput文件上传控件的亲身实践
2019/03/21 Javascript
Webpack中loader打包各种文件的方法实例
2019/09/03 Javascript
python新手经常遇到的17个错误分析
2014/07/30 Python
解决Python3中的中文字符编码的问题
2018/07/18 Python
python批量解压zip文件的方法
2019/08/20 Python
Python scipy的二维图像卷积运算与图像模糊处理操作示例
2019/09/06 Python
Python 使用元类type创建类对象常见应用详解
2019/10/17 Python
win7下 python3.6 安装opencv 和 opencv-contrib-python解决 cv2.xfeatures2d.SIFT_create() 的问题
2019/10/24 Python
django使用F方法更新一个对象多个对象字段的实现
2020/03/28 Python
PyCharm 2020.1版安装破解注册码永久激活(激活到2089年)
2020/09/24 Python
AssertionError 跟一下那个类是 “is – a”的关系
2012/02/21 面试题
文明餐桌行动实施方案
2014/02/19 职场文书
总经理人事任命书
2014/06/05 职场文书
地质工程专业毕业生求职信
2014/08/08 职场文书
领导班子奢靡之风查摆问题及整改措施
2014/09/27 职场文书
收费员岗位职责
2015/02/14 职场文书
mybatis 解决从列名到属性名的自动映射失败问题
2021/06/30 Java/Android