滴滴打車如何找出方圓一千米內的乘客?揭開GeoHash的神秘面紗

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 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都遠。這其實是一個邊緣性的問題……..後續我會更新如何去避免這種問題的出現

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章