日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

對于很多初學者來說,“附近的人”或者類似功能,在技術實現上還有點摸不著頭腦。本文將簡要的為你講解“附近的人”的基本理論原理,并以redis的GEO系列地理位置操作指令為例,理論聯系實際地為你講解它們是如何被高效實現的。

 

閱讀提示:本文適合有一定Redis使用經驗和經緯度知識的服務器后端開發人員閱讀。

 

經緯度常識

 

  • 經線是縱的,經度是橫的,用于表示不同的經線,緯線是橫的,緯度是縱的,用于表示不同的緯線,如下圖

 

GeoHash 算法

 


GeoHash 算法

 

  • 緯線:地球儀上的橫線,lat,赤道是最大的緯線,從赤道開始分為北緯和南緯,都是0-90°,緯線是角度數值,并不是米;
  • 經線:地球儀上的豎線,lng,子午線為0°,分為西經和東經,都是0-180°,經線也是角度數值;
  • 經緯線和米的換算:經度或者緯度0.00001度,約等于1米,這個在GPS測算距離的時候可以體會到,GPS只要精確到小數點后五位,就是10米范圍內的精度
  • 經度0度的位置為本初子午線,在180度的位置轉為西經,數字由大到小依次經過北美洲到達西歐.緯度0度的位置為赤道
  • 為了便于理解,將地球看成一個基于經緯度線的坐標系。緯線就是平行于赤道平面的那些平面的周線,經線就是連接南北兩極的大圓線的半圓弧。緯度分為北緯(正),南緯(負),赤道所在的緯度值為0。經度以本初子午線界(本初子午線經度為0),分為東經(正),西經(負)。故緯度范圍可表示為[-90o, 0o),(0o, 90o],經度范圍可表示為[-180o, 0o),(0o, 180o]

 

什么是geohash

 


 

  • GeoHash將二維的經緯度轉換成字符串,比如下圖展示了北京9個區域的GeoHash字符串,分別是WX4ER,WX4G2、WX4G3等等,每一個字符串代表了某一矩形區域。也就是說,這個矩形區域內所有的點(經緯度坐標)都共享相同的GeoHash字符串,這樣既可以保護隱私(只表示大概區域位置而不是具體的點),又比較容易做緩存。
GeoHash 算法

 

 

GeoHash 算法

 

  • 不同的編碼長度,表示不同的范圍區間,字符串越長,表示的范圍越精確
  • 字符串相似的表示距離相近(特殊情況后文闡述),這樣可以利用字符串的前綴匹配來查詢附近的POI信息。如下兩個圖所示,一個在城區,一個在郊區,城區的GeoHash字符串之間比較相似,郊區的字符串之間也比較相似,而城區和郊區的GeoHash字符串相似程度要低些

?

GeoHash 算法

 

?

GeoHash 算法

 

?

  • 總結:GeoHash就是一種將經緯度轉換成字符串的方法,并且使得在大部分情況下,字符串前綴匹配越多的距離越近

 

GeoHash 算法

 


 

以經緯度值:(116.389550, 39.928167)進行算法說明,對緯度39.928167進行逼近編碼 (地球緯度區間是[-90,90])

 

  1. 區間[-90,90]進行二分為[-90,0),[0,90],稱為左右區間,可以確定39.928167屬于右區間[0,90],給標記為1
  2. 接著將區間[0,90]進行二分為 [0,45),[45,90],可以確定39.928167屬于左區間 [0,45),給標記為0
  3. 遞歸上述過程39.928167總是屬于某個區間[a,b]。隨著每次迭代區間[a,b]總在縮小,并越來越逼近39.928167
  4. 如果給定的緯度x(39.928167)屬于左區間,則記錄0,如果屬于右區間則記錄1,序列的長度跟給定的區間劃分次數有關,如下圖

?

GeoHash 算法

 

?

GeoHash 算法

 

?

 

  • 同理,地球經度區間是[-180,180],可以對經度116.389550進行編碼
  • 通過上述計算,緯度產生的編碼為1 1 0 1 0 0 1 0 1 1 0 0 0 1 0,經度產生的編碼為1 0 1 1 1 0 0 0 1 1 0 0 0 1 1
  • 合并:偶數位放經度,奇數位放緯度,把2串編碼組合生成新串如下圖:
  • ?
  • 首先將11100 11101 00100 01111 0000 01101轉成十進制,對應著28、29、4、15,0,13 十進制對應的base32編碼就是wx4g0e,如下圖

?

GeoHash 算法

 

?

GeoHash 算法

 

?

  • Ø同理,將編碼轉換成經緯度的解碼算法與之相反

 

GeoHash 原理

 


 

  • Geohash其實就是將整個地圖或者某個分割所得的區域進行一次劃分,由于采用的是base32編碼方式,即Geohash中的每一個字母或者數字(如wx4g0e中的w)都是由5bits組成(2^5 = 32,base32),這5bits可以有32中不同的組合(0~31),這樣我們可以將整個地圖區域分為32個區域,通過00000 ~ 11111來標識這32個區域。第一次對地圖劃分后的情況如下圖所示(每個區域中的編號對應于該區域所對應的編碼):

?

GeoHash 算法

 

??

  • Geohash的0、1串序列是經度0、1序列和緯度0、1序列中的數字交替進行排列的,偶數位對應的序列為經度序列,奇數位對應的序列為緯度序列,在進行第一次劃分時,Geohash0、1序列中的前5個bits(11100),那么這5bits中有3bits是表示經度,2bits表示緯度,所以第一次劃分時,是將經度劃分成8個區段(2^3 = 8),將緯度劃分為4個區段(2^2 = 4),這樣就形成了32個區域。如下圖

?

GeoHash 算法

 

?

  • 同理,可以按照第一次劃分所采用的方式對第一次劃分所得的32個區域各自再次劃分.

 

對照

 

GeoHash 算法

 

?

GeoHash 算法

 


GeoHash 算法

 


GeoHash 算法

 

附近的人,附近的加油站如何實現

 

它需要做以下兩件事情:

 

1)在使用“附近的人”功能前提交自己的地理位置;

2)根據“我”的地理位置,計算出別人跟我的距離;

3)將第2步中計算出的距離由近及遠,進行排序。

 

具體在產品技術上的實現原理和技術難點

 

1)現在移動端(IOS、Android等),通過系統的API很容易抓到用戶當前的位置(即經緯度數據);

2)根據第1步中的經緯度數據,很容易計算出兩個點之間的距離

3)對第2步中的計算結果排序就更簡單了。

 

技術難點

 

1)如何高效地進行兩點距離的計算

2)如何高效地進行地理圍欄的圈定

 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; //經緯度單獨編碼長度

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' };

//定義編碼映射關系
final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
//初始化編碼映射內容
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;
}

//根據經緯度和范圍,獲取對應的二進制
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;
}

//將經緯度合并后的二進制進行指定的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;
    }
}

//根據二進制和范圍解碼
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();

    //偶數位,經度
    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);
    }

    //奇數位,緯度
    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]);
}
}
xxxxxxxxxxbr  public class GeoHash {brpublic static final double MINLAT = -90;brpublic static final double MAXLAT = 90;brpublic static final double MINLNG = -180;brpublic static final double MAXLNG = 180;brbrprivate static int numbits = 3 * 5; //經緯度單獨編碼長度brbrprivate static double minLat;brprivate static double minLng;brbrprivate final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',br        '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',br        'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };brbr//定義編碼映射關系brfinal static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();br//初始化編碼映射內容brstatic {br    int i = 0;br    for (char c : digits)br        lookup.put(c, i++);br}brbrpublic GeoHash(){br    setMinLatLng();br}brbrpublic String encode(double lat, double lon) {br    BitSet latbits = getBits(lat, -90, 90);br    BitSet lonbits = getBits(lon, -180, 180);br    StringBuilder buffer = new StringBuilder();br    for (int i = 0; i < numbits; i++) {br        buffer.append( (lonbits.get(i))?'1':'0');br        buffer.append( (latbits.get(i))?'1':'0');br    }br    String code = base32(Long.parseLong(buffer.toString(), 2));br    //Log.i("okunu", "encode  lat = " + lat + "  lng = " + lon + "  code = " + code);br    return code;br}brbrpublic ArrayList<String> getArroundGeoHash(double lat, double lon){br    //Log.i("okunu", "getArroundGeoHash  lat = " + lat + "  lng = " + lon);br    ArrayList<String> list = new ArrayList<>();br    double uplat = lat + minLat;br    double downLat = lat - minLat;brbr    double leftlng = lon - minLng;br    double rightLng = lon + minLng;brbr    String leftUp = encode(uplat, leftlng);br    list.add(leftUp);brbr    String leftMid = encode(lat, leftlng);br    list.add(leftMid);brbr    String leftDown = encode(downLat, leftlng);br    list.add(leftDown);brbr    String midUp = encode(uplat, lon);br    list.add(midUp);brbr    String midMid = encode(lat, lon);br    list.add(midMid);brbr    String midDown = encode(downLat, lon);br    list.add(midDown);brbr    String rightUp = encode(uplat, rightLng);br    list.add(rightUp);brbr    String rightMid = encode(lat, rightLng);br    list.add(rightMid);brbr    String rightDown = encode(downLat, rightLng);br    list.add(rightDown);brbr    //Log.i("okunu", "getArroundGeoHash list = " + list.toString());br    return list;br}brbr//根據經緯度和范圍,獲取對應的二進制brprivate BitSet getBits(double lat, double floor, double ceiling) {br    BitSet buffer = new BitSet(numbits);br    for (int i = 0; i < numbits; i++) {br        double mid = (floor + ceiling) / 2;br        if (lat >= mid) {br            buffer.set(i);br            floor = mid;br        } else {br            ceiling = mid;br        }br    }br    return buffer;br}brbr//將經緯度合并后的二進制進行指定的32位編碼brprivate String base32(long i) {br    char[] buf = new char[65];br    int charPos = 64;br    boolean negative = (i < 0);br    if (!negative){br        i = -i;br    }br    while (i <= -32) {br        buf[charPos--] = digits[(int) (-(i % 32))];br        i /= 32;br    }br    buf[charPos] = digits[(int) (-i)];br    if (negative){br        buf[--charPos] = '-';br    }br    return new String(buf, charPos, (65 - charPos));br}brbrprivate void setMinLatLng() {br    minLat = MAXLAT - MINLAT;br    for (int i = 0; i < numbits; i++) {br        minLat /= 2.0;br    }br    minLng = MAXLNG - MINLNG;br    for (int i = 0; i < numbits; i++) {br        minLng /= 2.0;br    }br}brbr//根據二進制和范圍解碼brprivate double decode(BitSet bs, double floor, double ceiling) {br    double mid = 0;br    for (int i=0; i<bs.length(); i++) {br        mid = (floor + ceiling) / 2;br        if (bs.get(i))br            floor = mid;br        elsebr            ceiling = mid;br    }br    return mid;br}brbr//對編碼后的字符串解碼brpublic double[] decode(String geohash) {br    StringBuilder buffer = new StringBuilder();br    for (char c : geohash.toCharArray()) {br        int i = lookup.get(c) + 32;br        buffer.append( Integer.toString(i, 2).substring(1) );br    }brbr    BitSet lonset = new BitSet();br    BitSet latset = new BitSet();brbr    //偶數位,經度br    int j =0;br    for (int i=0; i< numbits*2;i+=2) {br        boolean isSet = false;br        if ( i < buffer.length() )br            isSet = buffer.charAt(i) == '1';br        lonset.set(j++, isSet);br    }brbr    //奇數位,緯度br    j=0;br    for (int i=1; i< numbits*2;i+=2) {br        boolean isSet = false;br        if ( i < buffer.length() )br            isSet = buffer.charAt(i) == '1';br        latset.set(j++, isSet);br    }brbr    double lon = decode(lonset, -180, 180);br    double lat = decode(latset, -90, 90);brbr    return new double[] {lat, lon};br}brbrpublic static void main(String[] args)  throws Exception{br    GeoHash geohash = new GeoHash();br//        String s = geohash.encode(40.222012, 116.248283);br//        System.out.println(s);br    geohash.getArroundGeoHash(40.222012, 116.248283);br//        double[] geo = geohash.decode(s);br//        System.out.println(geo[0]+" "+geo[1]);br}b核心算法獲取任意兩點距離

核心算法獲取任意兩點距離

/**

 * 計算地球上任意兩點(經緯度)距離    

 *     

 * @param long1 第一點經度    

 * @param lat1 第一點緯度    

 * @param long2 第二點經度    

 * @param lat2 第二點緯度    

 * @return 返回距離 單位:米

 */

public static double Distance(double long1, double lat1, double long2, double lat2)

{

    double a, b, R;

    R = 6378137; // 地球半徑        

    lat1 = lat1 * Math.PI / 180.0;

    lat2 = lat2 * Math.PI / 180.0;

    a = lat1 - lat2;

    b = (long1 - long2) * Math.PI / 180.0;

    double d;

    double sa2, sb2;

    sa2 = Math.sin(a / 2.0);

    sb2 = Math.sin(b / 2.0);

    d = 2* R * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(lat1) * Math.cos(lat2) * sb2 * sb2));

    return d;

}
xxxxxxxxxxbr /**brbr * 計算地球上任意兩點(經緯度)距離    brbr *     brbr * @param long1 第一點經度    brbr * @param lat1 第一點緯度    brbr * @param long2 第二點經度    brbr * @param lat2 第二點緯度    brbr * @return 返回距離 單位:米brbr */brbrpublic static double Distance(double long1, double lat1, double long2, double lat2)brbr{brbr    double a, b, R;brbr    R = 6378137; // 地球半徑        brbr    lat1 = lat1 * Math.PI / 180.0;brbr    lat2 = lat2 * Math.PI / 180.0;brbr    a = lat1 - lat2;brbr    b = (long1 - long2) * Math.PI / 180.0;brbr    double d;brbr    double sa2, sb2;brbr    sa2 = Math.sin(a / 2.0);brbr    sb2 = Math.sin(b / 2.0);brbr    d = 2* R * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(lat1) * Math.cos(lat2) * sb2 * sb2));brbr    return d;brbr}

 

關注下我的公眾號吧,只為開源精神

分享到:
標簽:算法 GeoHash
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定