Geohash算法原理及實(shí)現(xiàn)

最近需要實(shí)現(xiàn)一個(gè)功能衣式,查找車輛附近的加油站寸士,如果車和加油站距離在200米以內(nèi),則查找成功碴卧。

加油站數(shù)量肯定不小弱卡,能否縮小查找范圍,否則以遍歷形式住册,效率肯定高不了婶博。

Geohash算法就是將經(jīng)緯度編碼,將二維變一維荧飞,給地址位置分區(qū)的一種算法凡人。

基本原理

GeoHash是一種地址編碼方法。他能夠把二維的空間經(jīng)緯度數(shù)據(jù)編碼成一個(gè)字符串

我們知道叹阔,經(jīng)度范圍是東經(jīng)180到西經(jīng)180挠轴,緯度范圍是南緯90到北緯90,我們設(shè)定西經(jīng)為負(fù)耳幢,南緯為負(fù)岸晦,所以地球上的經(jīng)度范圍就是[-180, 180]睛藻,緯度范圍就是[-90启上,90]。如果以本初子午線店印、赤道為界冈在,地球可以分成4個(gè)部分。

如果緯度范圍[-90°, 0°)用二進(jìn)制0代表按摘,(0°, 90°]用二進(jìn)制1代表讥邻,經(jīng)度范圍[-180°, 0°)用二進(jìn)制0代表,(0°, 180°]用二進(jìn)制1代表院峡,那么地球可以分成如下4個(gè)部分

如果在小塊范圍內(nèi)遞歸對半劃分呢兴使?

可以看到,劃分的區(qū)域更多了照激,也更精確了发魄。geohash算法就是基于這種思想,劃分的次數(shù)更多,區(qū)域更多励幼,區(qū)域面積更小了汰寓。通過將經(jīng)緯度編碼,給地理位置分區(qū)

Geohash算法

Geohash算法一共有三步苹粟。

首先將經(jīng)緯度變成二進(jìn)制有滑。

比如這樣一個(gè)點(diǎn)(39.923201, 116.390705)
緯度的范圍是(-90,90)嵌削,其中間值為0毛好。對于緯度39.923201,在區(qū)間(0苛秕,90)中肌访,因此得到一個(gè)1;(0艇劫,90)區(qū)間的中間值為45度吼驶,緯度39.923201小于45,因此得到一個(gè)0店煞,依次計(jì)算下去蟹演,即可得到緯度的二進(jìn)制表示,如下表:

最后得到緯度的二進(jìn)制表示為:

  10111000110001111001

同理可以得到經(jīng)度116.390705的二進(jìn)制表示為:

  11010010110001000100

第2步顷蟀,就是將經(jīng)緯度合并酒请。

經(jīng)度占偶數(shù)位,緯度占奇數(shù)位衩椒,注意蚌父,0也是偶數(shù)位哮兰。

  11100 11101 00100 01111 00000 01101 01011 00001

第3步毛萌,按照Base32進(jìn)行編碼

Base32編碼表的其中一種如下,是用0-9喝滞、b-z(去掉a, i, l, o)這32個(gè)字母進(jìn)行編碼阁将。具體操作是先將上一步得到的合并后二進(jìn)制轉(zhuǎn)換為10進(jìn)制數(shù)據(jù),然后對應(yīng)生成Base32碼右遭。需要注意的是做盅,將5個(gè)二進(jìn)制位轉(zhuǎn)換成一個(gè)base32碼。上例最終得到的值為

  wx4g0ec1

Geohash比直接用經(jīng)緯度的高效很多窘哈,而且使用者可以發(fā)布地址編碼吹榴,既能表明自己位于北海公園附近,又不至于暴露自己的精確坐標(biāo)滚婉,有助于隱私保護(hù)图筹。

  • GeoHash用一個(gè)字符串表示經(jīng)度和緯度兩個(gè)坐標(biāo)。在數(shù)據(jù)庫中可以實(shí)現(xiàn)在一列上應(yīng)用索引(某些情況下無法在兩列上同時(shí)應(yīng)用索引)
  • GeoHash表示的并不是一個(gè)點(diǎn),而是一個(gè)矩形區(qū)域
  • GeoHash編碼的前綴可以表示更大的區(qū)域远剩。例如wx4g0ec1扣溺,它的前綴wx4g0e表示包含編碼wx4g0ec1在內(nèi)的更大范圍。 這個(gè)特性可以用于附近地點(diǎn)搜索

編碼越長瓜晤,表示的范圍越小锥余,位置也越精確。因此我們就可以通過比較GeoHash匹配的位數(shù)來判斷兩個(gè)點(diǎn)之間的大概距離痢掠。

問題

geohash算法有兩個(gè)問題驱犹。首先是邊緣問題。

如圖志群,如果車在紅點(diǎn)位置着绷,區(qū)域內(nèi)還有一個(gè)黃點(diǎn)。相鄰區(qū)域內(nèi)的綠點(diǎn)明顯離紅點(diǎn)更近锌云。但因?yàn)辄S點(diǎn)的編碼和紅點(diǎn)一樣荠医,最終找到的將是黃點(diǎn)。這就有問題了桑涎。

要解決這個(gè)問題彬向,很簡單,只要再查找周邊8個(gè)區(qū)域內(nèi)的點(diǎn)攻冷,看哪個(gè)離自己更近即可娃胆。

另外就是曲線突變問題。

本文第2張圖片比較好地解釋了這個(gè)問題等曼。其中0111和1000兩個(gè)編碼非常相近里烦,但它們的實(shí)際距離確很遠(yuǎn)。所以編碼相近的兩個(gè)單位禁谦,并不一定真實(shí)距離很近胁黑,這需要實(shí)際計(jì)算兩個(gè)點(diǎn)的距離才行。

代碼實(shí)現(xiàn)

geohash原理清楚后州泊,代碼實(shí)現(xiàn)就比較簡單了丧蘸。不過仍然有一個(gè)問題需要解決,就是如何計(jì)算周邊的8個(gè)區(qū)域key值呢

假設(shè)我們計(jì)算的key值是6位遥皂,那么二進(jìn)制位數(shù)就是 6*5 = 30位力喷,所以經(jīng)緯度分別是15位。我們以緯度為例演训,緯度會均分15次弟孟。這樣我們很容易能夠算出15次后,劃分的最小單位是多少

  private void setMinLatLng() {
    minLat = MAXLAT - MINLAT;
    for (int i = 0; i < numbits; i++) {
        minLat /= 2.0;
    }
    minLng = MAXLNG - MINLNG;
    for (int i = 0; i < numbits; i++) {
        minLng /= 2.0;
    }
}

得到了最小單位样悟,那么周邊區(qū)域的經(jīng)緯度也可以計(jì)算得到了拂募。比如說左邊區(qū)域的經(jīng)度肯定是自身經(jīng)度減去最小經(jīng)度單位。緯度也可以通過加減,得到上下的緯度值没讲,最終周圍8個(gè)單位也可以計(jì)算得到眯娱。

可以到 http://geohash.co/ 進(jìn)行g(shù)eohash編碼,以確定自己代碼是否寫錯(cuò)

整體代碼如下所示:

public class GeoHash {
public static final double MINLAT = -90;
public static final double MAXLAT = 90;
public static final double MINLNG = -180;
public static final double MAXLNG = 180;

private static int numbits = 3 * 5; //經(jīng)緯度單獨(dú)編碼長度

private static double minLat;
private static double minLng;

private final static char[] digits = { '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' };

//定義編碼映射關(guān)系
final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
//初始化編碼映射內(nèi)容
static {
    int i = 0;
    for (char c : digits)
        lookup.put(c, i++);
}

public GeoHash(){
    setMinLatLng();
}

public String encode(double lat, double lon) {
    BitSet latbits = getBits(lat, -90, 90);
    BitSet lonbits = getBits(lon, -180, 180);
    StringBuilder buffer = new StringBuilder();
    for (int i = 0; i < numbits; i++) {
        buffer.append( (lonbits.get(i))?'1':'0');
        buffer.append( (latbits.get(i))?'1':'0');
    }
    String code = base32(Long.parseLong(buffer.toString(), 2));
    //Log.i("okunu", "encode  lat = " + lat + "  lng = " + lon + "  code = " + code);
    return code;
}

public ArrayList<String> getArroundGeoHash(double lat, double lon){
    //Log.i("okunu", "getArroundGeoHash  lat = " + lat + "  lng = " + lon);
    ArrayList<String> list = new ArrayList<>();
    double uplat = lat + minLat;
    double downLat = lat - minLat;

    double leftlng = lon - minLng;
    double rightLng = lon + minLng;

    String leftUp = encode(uplat, leftlng);
    list.add(leftUp);

    String leftMid = encode(lat, leftlng);
    list.add(leftMid);

    String leftDown = encode(downLat, leftlng);
    list.add(leftDown);

    String midUp = encode(uplat, lon);
    list.add(midUp);

    String midMid = encode(lat, lon);
    list.add(midMid);

    String midDown = encode(downLat, lon);
    list.add(midDown);

    String rightUp = encode(uplat, rightLng);
    list.add(rightUp);

    String rightMid = encode(lat, rightLng);
    list.add(rightMid);

    String rightDown = encode(downLat, rightLng);
    list.add(rightDown);

    //Log.i("okunu", "getArroundGeoHash list = " + list.toString());
    return list;
}

//根據(jù)經(jīng)緯度和范圍爬凑,獲取對應(yīng)的二進(jìn)制
private BitSet getBits(double lat, double floor, double ceiling) {
    BitSet buffer = new BitSet(numbits);
    for (int i = 0; i < numbits; i++) {
        double mid = (floor + ceiling) / 2;
        if (lat >= mid) {
            buffer.set(i);
            floor = mid;
        } else {
            ceiling = mid;
        }
    }
    return buffer;
}

//將經(jīng)緯度合并后的二進(jìn)制進(jìn)行指定的32位編碼
private String base32(long i) {
    char[] buf = new char[65];
    int charPos = 64;
    boolean negative = (i < 0);
    if (!negative){
        i = -i;
    }
    while (i <= -32) {
        buf[charPos--] = digits[(int) (-(i % 32))];
        i /= 32;
    }
    buf[charPos] = digits[(int) (-i)];
    if (negative){
        buf[--charPos] = '-';
    }
    return new String(buf, charPos, (65 - charPos));
}

private void setMinLatLng() {
    minLat = MAXLAT - MINLAT;
    for (int i = 0; i < numbits; i++) {
        minLat /= 2.0;
    }
    minLng = MAXLNG - MINLNG;
    for (int i = 0; i < numbits; i++) {
        minLng /= 2.0;
    }
}

//根據(jù)二進(jìn)制和范圍解碼
private double decode(BitSet bs, double floor, double ceiling) {
    double mid = 0;
    for (int i=0; i<bs.length(); i++) {
        mid = (floor + ceiling) / 2;
        if (bs.get(i))
            floor = mid;
        else
            ceiling = mid;
    }
    return mid;
}

//對編碼后的字符串解碼
public double[] decode(String geohash) {
    StringBuilder buffer = new StringBuilder();
    for (char c : geohash.toCharArray()) {
        int i = lookup.get(c) + 32;
        buffer.append( Integer.toString(i, 2).substring(1) );
    }

    BitSet lonset = new BitSet();
    BitSet latset = new BitSet();

    //偶數(shù)位徙缴,經(jīng)度
    int j =0;
    for (int i=0; i< numbits*2;i+=2) {
        boolean isSet = false;
        if ( i < buffer.length() )
            isSet = buffer.charAt(i) == '1';
        lonset.set(j++, isSet);
    }

    //奇數(shù)位,緯度
    j=0;
    for (int i=1; i< numbits*2;i+=2) {
        boolean isSet = false;
        if ( i < buffer.length() )
            isSet = buffer.charAt(i) == '1';
        latset.set(j++, isSet);
    }

    double lon = decode(lonset, -180, 180);
    double lat = decode(latset, -90, 90);

    return new double[] {lat, lon};
}

public static void main(String[] args)  throws Exception{
    GeoHash geohash = new GeoHash();
//        String s = geohash.encode(40.222012, 116.248283);
//        System.out.println(s);
    geohash.getArroundGeoHash(40.222012, 116.248283);
//        double[] geo = geohash.decode(s);
//        System.out.println(geo[0]+" "+geo[1]);
}
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘁信,一起剝皮案震驚了整個(gè)濱河市于样,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌潘靖,老刑警劉巖穿剖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異卦溢,居然都是意外死亡糊余,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門单寂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贬芥,“玉大人,你說我怎么就攤上這事宣决≌号” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵尊沸,是天一觀的道長威沫。 經(jīng)常有香客問我,道長洼专,這世上最難降的妖魔是什么棒掠? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮壶熏,結(jié)果婚禮上句柠,老公的妹妹穿的比我還像新娘浦译。我一直安慰自己棒假,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布精盅。 她就那樣靜靜地躺著帽哑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叹俏。 梳的紋絲不亂的頭發(fā)上妻枕,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼屡谐。 笑死述么,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的愕掏。 我是一名探鬼主播度秘,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饵撑!你這毒婦竟也來了剑梳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤滑潘,失蹤者是張志新(化名)和其女友劉穎垢乙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體语卤,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡追逮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粹舵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羊壹。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖齐婴,靈堂內(nèi)的尸體忽然破棺而出油猫,到底是詐尸還是另有隱情,我是刑警寧澤柠偶,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布情妖,位于F島的核電站,受9級特大地震影響诱担,放射性物質(zhì)發(fā)生泄漏毡证。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一蔫仙、第九天 我趴在偏房一處隱蔽的房頂上張望料睛。 院中可真熱鬧,春花似錦摇邦、人聲如沸恤煞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽居扒。三九已至,卻和暖如春丑慎,著一層夾襖步出監(jiān)牢的瞬間喜喂,已是汗流浹背瓤摧。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玉吁,地道東北人照弥。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像进副,于是被迫代替她去往敵國和親产喉。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1. 引言 GeoHash本質(zhì)上是空間索引的一種方式敢会,其基本原理是將地球理解為一個(gè)二維平面曾沈,將平面遞歸分解成更小的...
    renzehello閱讀 38,461評論 1 17
  • GeoHash算法 涉及到地圖的內(nèi)容,基本都會遇到搜索附近的功能鸥昏,比如附近的人塞俱、附近的店鋪等。要實(shí)現(xiàn)這樣的功能吏垮,我...
    小蘇c閱讀 2,014評論 0 2
  • 1.場景 隨著智能手機(jī)和傳感器技術(shù)的發(fā)展障涯,LBS(Location based service)類的應(yīng)用也逐漸多了...
    Daniel_adu閱讀 11,467評論 3 13
  • 膠州秧歌又稱地秧歌、跑秧歌膳汪,當(dāng)?shù)孛耖g稱扭斷腰唯蝶、三道彎,是山東省的漢族民俗舞蹈之一遗嗽,屬于三大秧歌之一粘我。膠州秧歌有23...
    中經(jīng)全媒體閱讀 731評論 0 0
  • 【垂釣前言】 氣象臺本周每天都有多次雷暴雨預(yù)警征字,但雨基本一閃而過;盡管預(yù)報(bào)周六有短時(shí)強(qiáng)降水和大風(fēng)娇豫,我們還是不太相信...
    huanbi4410閱讀 455評論 0 1