滴滴打车如何找出方圆一千米内的乘客?揭开 GeoHash 的神秘面纱
👉 这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
《项目实战(视频)》:从书中学,往事上“练” 《互联网高频面试题》:面朝简历学习,春暖花开 《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题 《精进 Java 学习指南》:系统学习,互联网主流技术栈 《必读 Java 源码专栏》:知其然,知其所以然
👉这是一个或许对你有用的开源项目
国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。
功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号、CRM 等等功能:
Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud 视频教程:https://doc.iocoder.cn 【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本
背景
不知道大家是否思考过一个问题,在一些场景下(如大家在使用高德地图打车的时候,邻近的司机是如何知道你在他的附近并将你的打车通知推送给他去接单的?)是如何实现的?
一般来讲,大家也许会想到,首先肯定需要知道每位乘客的经纬度(lng,lat),也即是二维坐标(当然这是在绝对理想的情况,不考虑上下坡度)。
而在知道了经纬度之后,一个暴力简单且容易想到的思路就是将经纬度这个「二元组」 都存放在一个「数组」 当中,然后当我们需要拿到离我们规定范围内的用户(如获取当前位置方圆百米内正在打车的乘客),我们就可以去遍历维护的那个数组,以此去判断数组中的经纬度与自己所在经纬度的距离,然后判断是否在范围内。
显然这种方法一定是能够达到目的的,但是值得注意的点是,维护的数据量一般来讲是海量的
,因此如果每次都需要遍历所有数据去进行计算,那这计算量以及存储量目前是无法满足的。那如何在此基础上去优化性能呢??那么这个内容就是本篇文章主要想探讨的问题......
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro 视频教程:https://doc.iocoder.cn/video/
GeoHash基本原理介绍
首先我想先介绍一下GeoHash这种算法「基本原理」 ,再讨论如何进行应用。
对于每一个坐标都有它的经纬度(lng,lat) ,而GeoHash的原理就是将经纬度先通过一个二分
的思路拿到一个二进制数组
的字符串,然后再通过base32编码
去进行压缩存储。
举一个例子,比如经纬度为(116.3111126,40.085003),对其进行二分步骤如下:
经度步骤:
bit | left | mid | right |
---|---|---|---|
1 | -180 | 0 | 180 |
1 | 0 | 90 | 180 |
0 | 90 | 135 | 180 |
1 | 90 | 112.5 | 135 |
0 | 112.5 | 123.75 | 135 |
0 | 112.5 | 118.125 | 123.75 |
1 | 112.5 | 115.3125 | 118.125 |
0 | 115.3125 | 116.71875 | 118.125 |
1 | 115.3125 | 116.015625 | 116.71875 |
0 | 116.015625 | 116.3671875 | 116.71875 |
1 | 116.015625 | 116.19140625 | 116.3671875 |
1 | 116.19140625 | 116.279296875 | 116.3671875 |
0 | 116.279296875 | 116.323242188 | 116.3671875 |
1 | 116.279296875 | 116.301269532 | 116.323242188 |
0 | 116.301269532 | 116.31225586 | 116.323242188 |
纬度步骤:
bit | left | mid | right |
---|---|---|---|
1 | -90 | 0 | 90 |
0 | 0 | 45 | 90 |
1 | 0 | 22.5 | 45 |
1 | 22.5 | 33.75 | 45 |
1 | 33.75 | 39.375 | 45 |
0 | 39.375 | 42.1876 | 45 |
0 | 39.375 | 40.78125 | 42.1876 |
1 | 39.375 | 40.078125 | 40.78125 |
0 | 40.078125 | 40.4296875 | 40.78125 |
0 | 40.078125 | 40.25390625 | 40.4296875 |
0 | 40.078125 | 40.166015625 | 40.25390625 |
0 | 40.078125 | 40.1220703125 | 40.166015625 |
0 | 40.078125 | 40.1000976563 | 40.1220703125 |
0 | 40.078125 | 40.0891113282 | 40.1000976563 |
1 | 40.078125 | 40.0836181641 | 40.0891113282 |
「其思路就是不断二分,如果原本值大于mid那本bit位就是1,以此往下递归,最终,我们递归二分得到纬度方向上的二进制字符串为 101110010000001,长度为 15 位」
那此时就拿到了30bit位的字符串,然后就开始进行拼接
结合经度字符串 110100101011010
和纬度字符串 101110010000001
,我们遵循先经度后纬度的顺序,逐一交错排列,最终得到的一维字符串为 11100 11101 00100 11000 10100 01001
.
然后再进行Base32编码,主要步骤就是首先会维护一个「0-9A-Za-z」 中32个字符的数组,如:['a','b','1','2','3','4','5','6','7','A'...],然后再将这30位的字符串每五个一组(正好覆盖0-31的索引)去索引到指定字符以此拿到30/5=6位的base32编码去进行存储。
「ps:注意并不一定是必要将经纬度都二分得到15位长度,多少位都可以,只是精度越高结果也就越精确,但是算力就越大,只需在此做出权衡即可」
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud 视频教程:https://doc.iocoder.cn/video/
GeoHash如何应用到这个问题当中?
上面讲到了可以通过GeoHash将经纬度转换成bit位的字符串,那么怎么进行应用呢,其实答案很明显,其实如果经纬度越接近,他们的前缀匹配位数也就越长,比如
通过这个思路我们就比较容易得到我们想要的范围内的乘客了。
遗留问题
但是其实仅仅如此是不够的,因为一个base32其实是覆盖了一片区域的,它并不是说仅仅代表一个精确的ip地址,那这其实就衍生出了一些问题,就比如
用geohash那结果显然是AB更近,但是实际上A与B的距离比AE、AC、AD都远。这其实是一个边缘性的问题........后续我会更新如何去避免这种问题的出现
欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,“长按”或“扫描”下方二维码噢:
星球的内容包括:项目实战、面试招聘、源码解析、学习路线。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
微信扫码关注该文公众号作者