1. 程式人生 > >GeoHash演算法獲取附近店鋪和距離

GeoHash演算法獲取附近店鋪和距離

GeoHash演算法將二維經緯度座標直接轉換成字串,每一個字串代表一個矩形區域,也就是說,這個矩形區域內所有的點(經緯度座標)都共享相同的GeoHash字串,字串的長度越大,矩形的區域就越小,經度也就越高。字串相似的表示距離相近,這樣可以利用字串的字首匹配來查詢附近的POI資訊

GeoHash演算法的步驟

地球緯度區間是[-90,90],經度區間是[-180,180],通過區間法對經度和緯度分別進行計算,假如我們獲取到的當前座標為經度-0.12866, 緯度38.534413,以緯度為例:

  1. 將緯度平均分成兩個區間:[-90,0),[0,90],成為左區間和右區間,可以判斷出38.534413屬於右區間,則值為1,(如果屬於左區間則值為0);
  2. 接著將右區間繼續劃分,就變成了[0,45),[45,90],此時,38.534413屬於左區間,則值為0
  3. 遞迴上述過程,則區間的值會越來越逼近38.534413
  4. 隨著演算法的進行,我們將會得到一個序列,序列的長度跟遞迴的次數有關,但是一定要保證的是經度和緯度的序列長度是一樣的,我這裡設定的遞迴長度是30,經度和緯度加起來就是60,
  5. 根據演算法我們最終得到經度的序列為[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0],緯度的序列為[1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0],然後我們根據此序列再組合一個新的序列偶數位放經度,奇數位放緯度,把2串編碼組合生成新串[0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0],實際上這個序列是一串二進位制
  6. 最後,我們將這個新串轉換成十進位制再使用0-9、b-z(去掉a, i, l, o)對這組十進位制進行編碼得到的字串eyzgjnfr4p0p就是最終的GeoHash編碼。下圖是網上給出的不同編碼長度給出的精度:

                   

得到GeoHash的值之後可以同樣的儲存近資料庫中,每次查詢離我最近的資料的時候理論上來說可以根據精確度擷取GeoHash的值進行模糊查詢,但是這樣查詢是有問題的,因為你沒法保證你每次查詢時你的當前座標剛好在這個矩形的正中間,如果你的座標處在矩形的邊界,那麼你就無法獲取其附近的資料,那麼這個問題怎麼解決呢?其實也很簡單,以你當前所在位置的矩形為九宮格的中間格子,再獲取其相鄰的8個矩形。

獲取其他8個區域的GeoHash也很簡單,上面的表已經給出了每一個區域內經度和緯度的寬度,那麼直接加減後就可以得出周邊相鄰的8個格子,我這裡自己寫了一套GeoHash的演算法,得出當前座標的GeoHash的值以及相鄰8個格子的值,程式碼比較多就不貼上來了,在GitHub上有一個別人寫好的JAVA版本的可以直接拿來用,叫geohash-java,貌似maven的倉庫中也有

使用的方法也很簡單:

/**
 * Created by linchaokun on 2018/4/9.
 */
public class GeohashUtil {
    private static  String format = "0.000000";
    private static final  double EARTH_RADIUS = 6371000;//赤道半徑(單位m)
    private static int numberOfCharacters = 12;

    /**
     * 根據經緯值得到Geohash字串
     *
     * @param lat
     *            緯度值
     * @param lon
     *            經度值
     * @return Geohash字串
     */
    public static String encode(double lat, double lon) {
        return getGeoHash(lat,lon,numberOfCharacters).toBase32();

    }

    /**
     * 根據經緯值得到Geohash字串
     *
     * @param lat
     *            緯度值
     * @param lon
     *            經度值
     * @param number
     *            經度 1-12
     * @return Geohash字串
     */
    public static String encode(double lat, double lon,int number) {
        return getGeoHash(lat,lon,number).toBase32();

    }

    /**
     * 獲取整個九宮格的GeoHash的值
     *
     * @param lat
     *            緯度值
     * @param lon
     *            經度值
     * @return Geohash字串集合
     */
    public static List<String> encodes(double lat, double lon){
        List<String> hashs = new ArrayList<>();
        GeoHash[] adjacent = getGeoHash(lat,lon,numberOfCharacters).getAdjacent();//獲取整個九宮格的GeoHash的值

        for (GeoHash hash : adjacent) {
            hashs.add(hash.toBase32());
        }

        return hashs;
    }

    /**
     * 獲取整個九宮格的GeoHash的值
     *
     * @param lat
     *            緯度值
     * @param lon
     *            經度值
     * @param number
     *            經度 1-12
     * @return Geohash字串集合
     */
    public static List<String> encodes(double lat, double lon,int number){
        List<String> hashs = new ArrayList<>();
        GeoHash[] adjacent = getGeoHash(lat,lon,number).getAdjacent();//獲取整個九宮格的GeoHash的值

        for (GeoHash hash : adjacent) {
            hashs.add(hash.toBase32());
        }

        return hashs;
    }

    /**
     * 根據GeoHash的值轉換為經緯度
     * @param geohash
     * @return
     */
    public static double[] decode(String geohash){

        GeoHash geoHash = GeoHash.fromGeohashString(geohash);
        WGS84Point point = geoHash.getPoint();
        double lat = point.getLatitude();
        double lon = point.getLongitude();

        DecimalFormat df = new DecimalFormat(format);

        return new double[]{Double.parseDouble(df.format(lat)),Double.parseDouble(df.format(lon))};
    }


    /**
     * 基於googleMap中的演算法得到兩經緯度之間的距離,計算精度與谷歌地圖的距離精度差不多,相差範圍在0.2米以下
     * @param lat1 第一點的精度
     * @param lng1 第一點的緯度
     * @param lat2 第二點的精度
     * @param lng2 第二點的緯度
     * @return 返回的距離,單位m
     * */

    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 s = EARTH_RADIUS * Math.PI * 2 * Math.asin(0.5 * lineDistance) / 180;
        return Math.round(s * 10000) / 10000;
    }

    private static GeoHash getGeoHash(double lat, double lon,int number){
        DecimalFormat df = new DecimalFormat(format);
        return GeoHash.withCharacterPrecision(Double.parseDouble(df.format(lat)),Double.parseDouble(df.format(lon)),number);
    }



    public static void main(String []args){
        //116.402843,39.999375  鳥巢   wx4g8c9v
        //116.3967,39.99932    水立方   wx4g89tk
        //116.40382,39.918118   故宮  wx4g0ffe
        double lon1=116.402843;
        double lat1=39.999375;
        double lon2=116.40382;
        double lat2=39.918118;
        double dist;
        String geocode;
        List<String> hashs = new ArrayList<>();



        dist = distance(lat1,lon1,lat2,lon2);
        System.out.println("兩點相距1:" + dist + " 米");

        hashs=encodes(lat1, lon1);
        System.out.println("當前位置編碼:" + hashs.toString());

        hashs=encodes(lat2, lon2);
        System.out.println("遠方位置編碼:" + hashs.toString());

        double[] decode = GeohashUtil.decode(encode(lat1, lon1));
        System.out.println(decode[0]+","+decode[1]);

    }
}

轉自 林朝昆部落格