geohash java github_GitHub - GongDexing/Geohash: GeoHash是目前比较主流实现位置服务的技术,用最简洁的Java实现GeoHash算法...
Geohash
GeoHash是目前比較主流實現位置服務的技術,Geohash算法將經緯度二維數據編碼為一個字符串,本質是一個降維的過程,
一個栗子
地點
經緯度
Geohash
鳥巢
116.402843,39.999375
wx4g8c9v
水立方
116.3967,39.99932
wx4g89tk
故宮
116.40382,39.918118
wx4g0ffe
水立方就在鳥巢在附近,距離600米左右,而故宮到鳥巢直線距離9公里左右,體現在Geohash上,鳥巢和水立方的前五位是一樣的,而鳥巢和故宮只有前4位是一樣的,也就是說Geohash前面相同的越多,兩個位置越近,但是反過來說,卻不一定正確,這個在后面會詳細介紹。
原理
將經緯度轉換為Geohash大體可以分為三步曲:
將緯度(-90, 90)平均分成兩個區間(-90, 0)、(0, 90),如果坐標位置的緯度值在第一區間,則編碼是0,否則編碼為1。我們用 39.918118 舉例,由于39.918118 屬于 (0, 90),所以編碼為1,然后我們繼續將(0, 90)分成(0, 45)、(45, 90)兩個區間,而39.918118 位于(0, 45),所以編碼是0,依次類推,我們進行20次拆分,最后計算39.918118 的編碼是 10111000110001011011;經度的處理也是類似,只是經度的范圍是(-180, 180),116.40382的編碼是11010010110001101010
經緯度的編碼合并,從0開始,奇數為是緯度,偶數為是經度,得到的編碼是1110011101001000111100000011100111001101
對經緯度合并后的編碼,進行base32編碼,最終得到wx4g0ffe
代碼實現
將經緯度轉換為二進制編碼
private void convert(double min, double max, double value, List list) {
if (list.size() > (length - 1)) {
return;
}
double mid = (max + min) / 2;
if (value < mid) {
list.add('0');
convert(min, mid, value, list);
} else {
list.add('1');
convert(mid, max, value, list);
}
}
合并經緯度的二進制編碼
List latList = new ArrayList();
List lngList = new ArrayList();
convert(Min_Lat, Max_Lat, lat, latList);
convert(Min_Lng, Max_Lng, lng, lngList);
StringBuilder sb = new StringBuilder();
for (int index = 0; index < latList.size(); index++) {
sb.append(lngList.get(index)).append(latList.get(index));
}
base32編碼
private final String[] base32Lookup =
{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "b", "c", "d", "e", "f", "g", "h",
"j", "k", "m", "n", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"};
private String base32Encode(final String str) {
String unit = "";
StringBuilder sb = new StringBuilder();
for (int start = 0; start < str.length(); start = start + 5) {
unit = str.substring(start, start + 5);
sb.append(base32Lookup[convertToIndex(unit.split(""))]);
}
return sb.toString();
}
private int convertToIndex(String str) {
int length = str.length();
int result = 0;
for (int index = 0; index < length; index++) {
result += str.charAt(index) == '0' ? 0 : 1 << (length - 1 - index);
}
return result;
}
邊界問題
兩個位置距離得越近是否意味著Geohash前面相同的越多呢?答案是否定的,兩個很近的地點[116.3967,44.9999]和[116.3967,45.0009]的Geohash分別是wxfzbxvr和y84b08j2,這就是Geohash存在的邊界問題,這兩個地點雖然很近,但是剛好在分界點45兩側,導致Geohash完全不同,單純依靠Geohash匹配前綴的方式并不能解決這種問題
在一維空間解決不了這個問題,回到二維空間中,將當前Geohash這塊區域周圍的八塊區域的Geohash計算出來
[116.3967,44.9999] 周圍8塊區域的Geohash
y84b08j2, wxfzbxvq, wxfzbxvx, wxfzbxvp, y84b08j8, y84b08j0, wxfzbxvw, wxfzbxvn
[116.3967,45.0009] 周圍8塊區域的Geohash
y84b08j3, wxfzbxvr, y84b08j8, y84b08j0, y84b08j9, y84b08j1, wxfzbxvx, wxfzbxvp
[116.3967,44.9999]和[116.3967,45.0009]分別出現在各自附近的區域中,周圍8個區域的Geohash怎么計算得到呢?很簡單,當Geohash長度是8時,對應的每個最小單元
double latUnit = (Max_Lat - Min_Lat) / (1 << 20);
double lngUnit = (Max_Lng - Min_Lng) / (1 << 20);
這樣可以計算出8個分別分布在周圍8個區域的地點,根據地點便可以計算出周圍8個區域的Geohash
[lat + latUnit, lng]
[lat - latUnit, lng]
[lat, lng + lngUnit]
[lat, lng - lngUnit]
[lat + latUnit, lng + lngUnit]
[lat + latUnit, lng - lngUnit]
[lat - latUnit, lng + lngUnit]
[lat - latUnit, lng - lngUnit]
距離還是距離
打開餓了么這樣的應用,除了可以看到附近的商家外,還能清晰看到離每個商家的距離,這個距離的怎么計算出呢?這完全是一個數學問題,把地球看著一個球體,先根據經緯度算出空間坐標,進而算出兩點直線距離,最后算出弧長,便是兩個位置的距離
public static double distance(double lat1, double lng1, double lat2, double lng2) {
double x1 = Math.cos(lat1) * Math.cos(lng1);
double y1 = Math.cos(lat1) * Math.sin(lng1);
double z1 = Math.sin(lat1);
double x2 = Math.cos(lat2) * Math.cos(lng2);
double y2 = Math.cos(lat2) * Math.sin(lng2);
double z2 = Math.sin(lat2);
double lineDistance =
Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2));
double realDistance = EARTH_RADIUS * Math.PI * 2 * Math.asin(0.5 * lineDistance) / 180;
return realDistance;
}
在實際應用中,先根據Geohash篩選出附近的地點,然后再算出距離附近地點的距離
總結
以上是生活随笔為你收集整理的geohash java github_GitHub - GongDexing/Geohash: GeoHash是目前比较主流实现位置服务的技术,用最简洁的Java实现GeoHash算法...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 治输卵管堵塞做造影多少钱
- 下一篇: 疏通管道多少钱啊?