app后端开发四:GeoHash实现查找附近的X
来源:互联网 发布:linux proc 共享内存 编辑:程序博客网 时间:2024/06/02 23:01
夜以继日,从7月28,到今天此时此刻,用laravel做的app接口,1.0版本终于做完了。后面等整个项目上线了,可以慢慢的来回顾一下这次开发的过程。今天主要来说下附近的X这个功能。
情景描述
现在附近的人、附近的店、非常的有用。之前一直在思考这个东西应该怎么做,怎么来实现它。要实现这个功能的逻辑,非常简单,这里我们以查找附近的店距离。
首先要做的是,查找出平台所有店铺。然后根据经纬度,算出app使用者与这些店铺的距离。然后对计算出来的距离进行排序。这个排序结构就是由近及远的一个结果。
上面的问题
看起来这个问题得到了解决。那么问题来了。如果你做的应用,里边店铺有100万家,那么你首先要从数据库(mysql为例)把所有店铺的经纬度读出来。然后利用php代码,根据app使用者的经纬度,计算出100万家的距离,然后再排序。我都不忍心往下说了,大家想想这个效率会是一个什么情况。
这种方法,无法利用数据库的分页、条件查找等,大大增加了数据的负担。解决这个问题的方法很多。今天我只说 GeoHash 的解决方案。
什么是GeoHash
简单说,Geohash算法;geohash是一种地址编码,它能把二维的经纬度编码成一维的字符串。
如果想要了解的详细点:可以查看该文章:GeoHash核心原理解析
如果你不太想了解它实现的细枝末节,一点都不会影响到你的使用。因为它的实现,已经实现好了。你通过它的实现类,可以轻易的完成使用。PS:所有代码,会放在github上,链接在文章底部
来说一下它的优缺点。
优点:
- 利用一个字段,便可存储经纬度;搜索时,只需一条索引,效率较高
- 编码的前缀可以表示更大的区域,查找附近的,非常方便。 SQL中,LIKE ‘wm3yr3%’,即可查询附近的所有地点,可以进行分页,对数据库压力大大降低
- 通过编码精度可模糊坐标、隐私保护等。
缺点: 距离和排序需二次运算(筛选结果中运行,其实挺快)
但是这里还有一个问题需要注意,那就是GeoHash它的编码规则是,将一个区域分成九宫格,那如果当某一个用户在使用的时候,刚好处在九宫格的边界上,那么问题就来了。
如上图,假设用户在绿色点的位置,明显的B距离他更近。但是在查询的时候会发现距离较远店铺的GeoHash编码与用户一样(因为在同一个GeoHash区域块上),而较近餐馆的GeoHash编码与我们不一致。这个问题往往产生在边界处。
这种问题的解决方案是,当我们需要获取绿色位置附近的店铺时,不要单单只查找它所在的区域,而要同时查找它附近的八个区域。也就是要查找途中最大的矩形所有区域。具体实现,我也做好了,你使用的时候,只需调用一个函数即可。
使用策略
该说的理论基本上都说完了,现在来说一说实际的操作过程。
首先,在添加店铺的数据时,当对店铺数据入库时,需要同时将店铺的经纬度进行geohash编码,存入数据库中(因为店铺位置基本不会改变)。
当用户使用附近的店查找时,从客户端上传上来用户的经纬度,然后进行geohash编码,通过sql:
where left(`geohash`,5) in('wm3yx','wm3yp','wm6n2','wm3yq','wm3yw','wm6n8','wm6n0','wm3yn','wm3yr')
查找附近相应的店铺。
查找完成后,如果需要继续出距离,则,可以使用下面的方法,求出当前位置与目标位置的距离
/** * * * @desc 地理位置信息 */class Location{ // 地球的求半径,单位还是m const EARTH_RADIUS = 6378137; /** * 经纬度转化为幅度 * @param string $d * @return number */ private static function fnRad($d) { return $d * pi() / 180.0; } /** * 计算两点之间的距离,单位m * latitude(-90,90) * longitude(-180,180) * @param string $lnglat1 * @param string $lnglat2 */ public static function getP2PDistance($srcLongLat, $destLongLat) { $srcLongLat = explode(',', $srcLongLat); $destLongLat = explode(',', $destLongLat); list($lat1, $lng1) = $srcLongLat; list($lat2, $lng2) = $destLongLat; //return self::googleDistance($lat1, $lng1, $lat2, $lng2); return self::selfDistance($lat1, $lng1, $lat2, $lng2); } /** * 自定义算法 * 效率更高 */ private static function selfDistance($lat1, $lng1, $lat2, $lng2) { //将角度转为狐度 $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))*self::EARTH_RADIUS; //精度 $s = round($s* 10000)/10000; return round($s); } /** * google的算法 * 效率稍微差一点 */ private static function googleDistance($lat1, $lng1, $lat2, $lng2) { // 通过纬度取得对应的幅度 $srcRadLat = self::fnRad($lat1); $destRadLat = self::fnRad($lat2); // 获取两点纬度弧度差 $a = $srcRadLat - $destRadLat; // 获取两点经度的弧度差 $b = self::fnRad($lng1) - self::fnRad($lng2); // 计算球体上该弧度对应的距离 $s = 2 * asin(sqrt(pow(sin($a/2),2) + cos($srcRadLat)*cos($destRadLat)*pow(sin($b/2),2))) * self::EARTH_RADIUS; // 取得距离的km数 $s = round($s * 10000) / 10000; return round($s); }}
在计算距离的方法中,使用了两种方法,一种是推导出的数学公式,一种是google给出的算法,最后测试发现推导的数学公式效率更高。不信的可以动手试试。至于计算完,距离,如何排序就不多了。
geohash类的方法介绍,请参考github上的文档。
项目地址:https://github.com/helei112g/laravel_geohash
app后端开发系列文章目录
- app后端开发四:GeoHash实现查找附近的X
- geohash算法,实现快速查找附近的点
- 查找附近网点geohash算法及实现
- 查找附近网点geohash算法及实现 (PHP版本)
- 查找附近网点geohash算法及实现 (Java版本)
- 查找附近的xxx 球面距离以及Geohash方案探讨
- 查找附近的xxx 球面距离以及Geohash方案探讨
- 查找附近的xxx 球面距离以及Geohash方案探讨
- 查找附近的xxx 球面距离以及Geohash方案探讨
- 查找附近的xxx 球面距离以及Geohash方案探讨
- 查找附近的xxx 球面距离以及Geohash方案探讨
- 查找附近的xxx 球面距离以及Geohash方案探讨
- 根据GEOHASH,查找附近的人,判断距离远
- 开发LBS应用之 根据一点的经纬度实现附近点的查询 - geohash
- 查找附近点--Geohash方案讨论
- 查找附近点--Geohash方案讨论
- 查找附近点–GEOHASH方案讨论
- 查找附近点--Geohash方案讨论
- How Garbage Collection Really Works
- 关于本博客和本人的说明
- ios-runtime原理
- 【Windows编程】系列第十一篇:多文档界面框架
- 【IT面经】献给正在找工作的程序员
- app后端开发四:GeoHash实现查找附近的X
- Unique Binary Search Trees
- 减少linux服务器大量TIME_WAIT
- Objective-C类,NSBundle介绍和使用
- PAT(B) 1029. 旧键盘
- C++中的智能指针
- Linux下screen的使用
- Echarts字符云tooltip显示混乱问题的解决办法
- android状态选择器小技巧