网站地图    收藏   

主页 > php专栏 > php综合实列 >

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

来源:自学PHP网    时间:2019-08-01 11:26 作者:小飞侠 阅读:

[导读] PHP进阶学习之Geo的地图定位算法详解...

本文实例讲述了PHP进阶学习之Geo的地图定位算法。分享给大家供大家参考,具体如下:
前言查找某个物体的定位,或者查找附近的范围等,我们自然而然会想到的方法就是利用各种提供服务的地图网站的API,基于API,用经纬度去实现定位和查找附近范围等等。然而,由于原理没有做一个了解和一定的认识,在对比距离远近关系或者控制精确程度方面,我们并不了解怎么利用这些经纬度数值去实现距离转化和对比。本章节我们就来探讨一下基于geo的位置算法原理。概念

  • 纬线:纬线是与地轴垂直的线,着东西方向环绕地球一周,所有的纬度都是平行的。其中,赤道是最长的纬线,纬度为0度,纬线数值是角度数值,从赤道开始分为北纬和南纬,都是0-90°;地球上任意一点都可以用经纬度这样两个维度去唯一确定。如果我们把二维平面上的所有点都用一个数字表示,即经纬度换算成一个字符串,则可以转为一维坐标来表示,大大减少计算量。这就是现在应用广泛的geoHash。:Geohash是公共领域地理编码系统,它将地理位置编码为一串字母和数字。Geohash提供了像任意精度这样的属性,以及逐渐从代码末尾删除字符以减小其大小(并逐渐失去精度)的可能性。由于逐步精度下降的结果,附近的地方往往(但不总是)呈现类似的前缀。共享前缀越长,两个地方越接近。原理
    1. 根据经纬度计算GeoHash二进制编码(以经纬度值:(116.389550,39.928167)进行算法说明)递归上述过程39.928167总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,并越来越逼近39.928167;
      2.4 这样随着算法的进行会产生一个序列1011100011的纬度二进制编码;
    2. 同理,计算出地球经度二进制,区间是[-180,180],可以对经度116.389550进行编码。算出结果1101001011;
    3. 组码:通过上述计算,纬度产生的编码为10111 00011,经度产生的编码为11010 01011。偶数位放经度,奇数位放纬度,把2串编码组合生成新串:11100 11101 00100 01111。使用用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,首先将11100 11101 00100 01111转成十进制,对应着28、29、4、15,十进制对应的编码就是wx4g。
      进行多次分解后,我们就可以得到更精确的位置划分,如上述计算的wx4g已经可以精确到一个城市城区了:
      从上图中可以看出,相邻城区的geoHash值的共同前缀越多,由此我们就可以应用共同前缀来判断附近相邻的位置了。当然精确范围也是根据经纬度和hash值的范围来确定的,如下表,geo精确到8位的共同前缀,可以表示附近约20米内的范围了:
      在PHP中的实现与应用
    4. 利用现成的地图API百度、高德等,很多免费API可以使用;如需更大更精确的范围,可以使用google的geo api,缺点就是每日请求次数有限制,如果是企业级别的应用,付费增加请求次数的允许权限是必不可少的。可查阅链接:https://developers.google.com/maps/documentation/geocoding/start MongoDB,适合在国内云平台直接使用。如果是AWS平台,也提供了dynamodb这种NoSQL存储组件。这些存储组件均可以直接传入经纬度,自动换算为geoHash落地存储,也提供了直接计算距离,搜索范围数据返回的功能。在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]);
      
      :这种方式计算较为复杂,即是根据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; $icoding[$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);
      }总结具体实现方案还需以实际业务需要为准。如果属于精确度要求很高或者企业级的大规模应用,可以首先考虑MongoDB或者其他提供Geo功能的存储组件,如果较为轻量级,可以借助第三方地区API、或者利用redis做geo的简单应用。如果业务需求复杂度不高,在这里并不推荐直接使用PHP写,毕竟效率会比较低,而且这也不是业务关注的重点,所以没必要重新造轮子。
      php面向对象程序设计入门教程》、《PHP数组(Array)操作技巧大全》、《PHP基本语法入门教程》、《PHP运算与运算符用法总结》、《php字符串(string)用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》

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

    自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习

    京ICP备14009008号-1@版权所有www.zixuephp.com

    网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com

    添加评论