Redis GEO

拿客_三產發表於2017-06-20

簡介

Redis 3.2 版本提供了GEO(地理資訊定位)功能,支援儲存地理位置資訊用來實現諸如附近位置、搖一搖這類依賴於地理位置資訊的功能,對於需要實現這些功能的開發者來說是一大音。GEO功能是 Redis 的另一位作者Matt Stancliff 借鑑 NoSQL 資料庫 Ardb 實現的,Ardb 的作者來自中國,它提供了優秀的GEO功能。

相關命令

增加地理位置資訊

GEOADD

自3.2.0可用。

時間複雜度:每新增一個元素的複雜度為 O(log(N)) , 其中 N 為鍵裡面包含的位置元素數量。

語法:GEOADD key longitude latitude member [longitude latitude member …]
說明:

longitudelatitudemember 分別是該地理位置的經度、緯度、成員(就是名稱)。

將給定的空間元素(緯度、經度、名字)新增到指定的鍵裡面。 這些資料會以有序集合的形式被儲存在鍵裡面, 從而使得像 GEORADIUSGEORADIUSBYMEMBER 這樣的命令可以在之後通過位置查詢取得這些元素。

GEOADD 命令以標準的 x,y 格式接受引數, 所以使用者必須先輸入經度, 然後再輸入緯度。 GEOADD 能夠記錄的座標是有限的: 非常接近兩極的區域是無法被索引的。 精確的座標限制由 EPSG:900913 / EPSG:3785 / OSGEO:41001 等座標系統定義, 具體如下:

  • 有效的經度介於 -180 度至 180 度之間。
  • 有效的緯度介於 -85.05112878 度至 85.05112878 度之間。

當使用者嘗試輸入一個超出範圍的經度或者緯度時, GEOADD 命令將返回一個錯誤。

返回值:

新新增到鍵裡面的空間元素數量, 不包括那些已經存在但是被更新的元素。

示例:
# http://api.map.baidu.com/lbsapi/getpoint/index.html 這裡可以獲取座標,我們填入 天安門 香山 兩個位置的座標
 coderknock> GEOADD tour 116.404412 39.915046 TianAnMen 116.20003 40.002428 XiangShan
(integer) 2
# 新增北京電影學院、毛主席紀念堂,更新 天安門座標(返回值不包括更新的個數)
 coderknock> GEOADD tour 116.362444 39.977552 BeijingFilmAcademy 116.404269 39.909179 ChairmanMaoZedongMemorialHall 116.404412 39.915046 TianAnMen
(integer) 2

獲取地理位置

GEOPOS

自3.2.0可用。

時間複雜度:獲取每個位置元素的複雜度為 O(log(N)) , 其中 N 為鍵裡面包含的位置元素數量。

語法:GEOPOS key member [member …]
說明:

從鍵裡面返回所有給定位置元素的位置(經度和緯度)。

因為 GEOPOS 命令接受可變數量的位置元素作為輸入, 所以即使使用者只給定了一個位置元素, 命令也會返回陣列回覆。

返回值:

GEOPOS 命令返回一個陣列, 陣列中的每個項都由兩個元素組成: 第一個元素為給定位置元素的經度, 而第二個元素則為給定位置元素的緯度。

當給定的位置元素不存在時, 對應的陣列項為空值。

示例:
# key 不存在
 coderknock> GEOPOS nonKey
(empty list or set)
# 查詢天安門、毛主席紀念堂的位置資訊
 coderknock> GEOPOS tour TianAnMen ChairmanMaoZedongMemorialHall
1) 1) "116.40441387891769"
   2) "39.915046196472751"
2) 1) "116.40426903963089"
   2) "39.909178316988886"

獲取兩個地理位置的距離

GEODIST

自3.2.0可用。

時間複雜度:O(log(N))。

語法: GEODIST key member1 member2 [unit]
說明:

返回兩個給定位置之間的距離。

如果兩個位置之間的其中一個不存在, 那麼命令返回空值。

指定單位的引數 unit 必須是以下單位的其中一個:

  • m 表示單位為米。
  • km 表示單位為千米。
  • mi 表示單位為英里。
  • ft 表示單位為英尺。

如果使用者沒有顯式地指定單位引數, 那麼 GEODIST 預設使用米作為單位。

GEODIST 命令在計算距離時會假設地球為完美的球形, 在極限情況下, 這一假設最大會造成 0.5% 的誤差。

返回值:

計算出的距離會以雙精度浮點數的形式被返回。 如果給定的位置元素不存在, 那麼命令返回空值。

示例:
# 查詢天安門 到 毛主席紀念堂的距離,使用預設單位 m
 coderknock> GEODIST tour TianAnMen ChairmanMaoZedongMemorialHall
"652.7795"
# 查詢天安門 到 毛主席紀念堂的距離,使用單位 km    
 coderknock>  GEODIST tour TianAnMen ChairmanMaoZedongMemorialHall km
"0.6528"
# 查詢的第二個元素不存在,返回 nil
 coderknock>  GEODIST tour TianAnMen non
(nil)

GEOHASH

GEOHASH

自3.2.0可用。

時間複雜度:尋找每個位置元素的複雜度為 O(log(N)) , 其中 N 為給定鍵包含的位置元素數量。

語法:GEOHASH key member [member …]
說明:

返回一個或多個位置元素的 Geohash 表示

返回值:

一個陣列, 陣列的每個項都是一個 GEOHash 。 命令返回的GEOHash 的位置與使用者給定的位置元素的位置一一對應。

示例:
# GEOHash 通過演算法可以轉成 經緯度格式
 coderknock> GEOHASH tour TianAnMen
1) "wx4g0f71gr0"

GEOHash 有如下特點:

  • GEO的資料型別為 zset,Redis 將所有地理位置資訊的 GEOHash 存放在 zset 中。

     coderknock> TYPE tour
    zset
  • 字串越長,表示的位置更精確,例如 GEOHash 長度為9時,精度在2米左右。
GEOHash 長度 精度(km)
1 2500
2 630
3 78
4 20
5 2.4
6 0.61
7 0.076
8 0.019
9 0.002
  • 兩個字串越相似,它們之間的距離越近,Redis 利用字串字首匹配演算法實現相關的命令。
  • GEOHash 編碼和經緯度是可以相互轉換的。

下面是 Java 版本的演算法:

import jodd.util.Base32;

import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;

/**
 * <p> 本程式碼來自 http://www.cnblogs.com/gaopeng527/p/5066983.html </p>
 *
 * @author 三產
 * @version 1.0
 * @date 2017-06-20
 * @QQGroup 213732117
 * @website http://www.coderknock.com
 * @copyright Copyright 2017 拿客 coderknock.com  All rights reserved.
 * @since JDK 1.8
 */
public class GeoHash {
    private static int numbits = 6 * 5; //經緯度單獨編碼長度
    //32位編碼對應字元
    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++);
    }

    /**
     * 對編碼後的字串解碼
     *
     * @param geohash
     * @return
     */
    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};
    }

    /**
     * 根據二進位制和範圍解碼
     *
     * @param bs
     * @param floor
     * @param ceiling
     * @return
     */
    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;
    }

    /**
     * 對經緯度進行編碼
     *
     * @param lat
     * @param lon
     * @return
     */
    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`);
        }
        return base32(Long.parseLong(buffer.toString(), 2));
    }

    /**
     * 根據經緯度和範圍,獲取對應二進位制
     *
     * @param lat
     * @param floor
     * @param ceiling
     * @return
     */
    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位編碼
     *
     * @param i
     * @return
     */
    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));
    }

    public static void main(String[] args) throws Exception {
        GeoHash geohash = new GeoHash();
        String s = geohash.encode(39.9150413274765, 116.40440583229065);
        System.out.println(s);
        double[] geo = geohash.decode(s);
        System.out.println(geo[0] + " " + geo[1]);
        System.out.println(Arrays.toString(geohash.decode("wx4g0f71gr0")));
    }
}

獲取指定位置範圍內的地理資訊位置集合

GEORADIUS

自3.2.0可用。

時間複雜度:O(N+log(M)), 其中 N 為指定半徑範圍內的位置元素數量, 而 M 則是被返回位置元素的數量。

語法:GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD][WITHDIST][WITHHASH][ASC|DESC][COUNT count][STORE key][STOREDIST key]
說明:

以給定的經緯度為中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素。

範圍可以使用以下其中一個單位:

  • m 表示單位為米。
  • km 表示單位為千米。
  • mi 表示單位為英里。
  • ft 表示單位為英尺。

在給定以下可選項時, 命令會返回額外的資訊:

  • WITHDIST : 在返回位置元素的同時, 將位置元素與中心之間的距離也一併返回。 距離的單位和使用者給定的範圍單位保持一致。
  • WITHCOORD : 將位置元素的經度和維度也一併返回。
  • WITHHASH : 以 52 位有符號整數的形式, 返回位置元素經過原始 geohash 編碼的有序集合分值。 這個選項主要用於底層應用或者除錯, 實際中的作用並不大。

命令預設返回未排序的位置元素。 通過以下兩個引數, 使用者可以指定被返回位置元素的排序方式:

  • ASC : 根據中心的位置, 按照從近到遠的方式返回位置元素。
  • DESC : 根據中心的位置, 按照從遠到近的方式返回位置元素。

在預設情況下, GEORADIUS 命令會返回所有匹配的位置元素。 雖然使用者可以使用 COUNT <count> 選項去獲取前 N 個匹配元素, 但是因為命令在內部可能會需要對所有被匹配的元素進行處理, 所以在對一個非常大的區域進行搜尋時, 即使只使用 COUNT 選項去獲取少量元素, 命令的執行速度也可能會非常慢。 但是從另一方面來說, 使用 COUNT 選項去減少需要返回的元素數量, 對於減少頻寬來說仍然是非常有用的。

  • STORE key :將返回結果的地理位置資訊儲存到指定鍵。
  • STOREDIST key :將返回結果離中心節點的距離儲存到指定鍵。
返回值:

GEORADIUS 命令返回一個陣列, 具體來說:

  • 在沒有給定任何 WITH 選項的情況下, 命令只會返回一個像 ["New York","Milan","Paris"] 這樣的線性(linear)列表。
  • 在指定了 WITHCOORDWITHDISTWITHHASH 等選項的情況下, 命令返回一個二層巢狀陣列, 內層的每個子陣列就表示一個元素。

在返回巢狀陣列時, 子陣列的第一個元素總是位置元素的名字。 至於額外的資訊, 則會作為子陣列的後續元素, 按照以下順序被返回:

  1. 以浮點數格式返回的中心與位置元素之間的距離, 單位與使用者指定範圍時的單位一致。
  2. geohash 整數。
  3. 由兩個元素組成的座標,分別為經度和緯度。
示例:
# 檢視 tour 中距離北京南站 100 km 以內的景點
 coderknock> GEORADIUS tour 116.385871 39.871977 100 km
1) "XiangShan"
2) "BeijingFilmAcademy"
3) "ChairmanMaoZedongMemorialHall"
4) "TianAnMen"
# 檢視 tour 中距離北京南站 10 km 以內的景點同時顯示其經緯度
 coderknock> GEORADIUS tour 116.385871 39.871977 10 km WITHCOORD

1) 1) "ChairmanMaoZedongMemorialHall"
   2) 1) "116.40426903963089"
      2) "39.909178316988886"
2) 1) "TianAnMen"
   2) 1) "116.40441387891769"
      2) "39.915046196472751"
# 檢視 tour 中距離北京南站 20 km 以內的景點同時顯示其經緯度以及距離北京南站的距離(這裡的單位是 km)
 coderknock> GEORADIUS tour 116.385871 39.871977 20 km WITHCOORD WITHDIST
1) 1) "BeijingFilmAcademy"
   2) "11.9116"
   3) 1) "116.36244267225266"
      2) "39.97755242026205"
2) 1) "ChairmanMaoZedongMemorialHall"
   2) "4.4256"
   3) 1) "116.40426903963089"
      2) "39.909178316988886"
3) 1) "TianAnMen"
   2) "5.0450"
   3) 1) "116.40441387891769"
      2) "39.915046196472751"
# 檢視 tour 中距離北京南站 20 km 以內的景點同時顯示其經緯度以及距離北京南站的距離(這裡的單位是 km)並且按從近到遠排序 
 coderknock> GEORADIUS tour 116.385871 39.871977 20 km WITHCOORD WITHDIST ASC
1) 1) "ChairmanMaoZedongMemorialHall"
   2) "4.4256"
   3) 1) "116.40426903963089"
      2) "39.909178316988886"
2) 1) "TianAnMen"
   2) "5.0450"
   3) 1) "116.40441387891769"
      2) "39.915046196472751"
3) 1) "BeijingFilmAcademy"
   2) "11.9116"
   3) 1) "116.36244267225266"
      2) "39.97755242026205" 
# 檢視 tour 中距離北京南站 20 km 以內的景點同時顯示其經緯度以及距離北京南站的距離(這裡的單位是 km)並且按從遠到近排序       
 coderknock>  GEORADIUS tour 116.385871 39.871977 20 km WITHCOORD WITHDIST DESC
1) 1) "BeijingFilmAcademy"
   2) "11.9116"
   3) 1) "116.36244267225266"
      2) "39.97755242026205"
2) 1) "TianAnMen"
   2) "5.0450"
   3) 1) "116.40441387891769"
      2) "39.915046196472751"
3) 1) "ChairmanMaoZedongMemorialHall"
   2) "4.4256"
   3) 1) "116.40426903963089"
      2) "39.909178316988886"     
# 檢視 tour 中距離北京南站 20 km 以內的景點同時顯示其經緯度以及距離北京南站的距離(這裡的單位是 km)並且按從遠到近排序 並且顯示 geohash 整數
 coderknock> GEORADIUS tour 116.385871 39.871977 20 km WITHCOORD WITHDIST DESC WITHHASH
1) 1) "BeijingFilmAcademy"
   2) "11.9116"
   3) (integer) 4069880657711258
   4) 1) "116.36244267225266"
      2) "39.97755242026205"
2) 1) "TianAnMen"
   2) "5.0450"
   3) (integer) 4069885555136394
   4) 1) "116.40441387891769"
      2) "39.915046196472751"
3) 1) "ChairmanMaoZedongMemorialHall"
   2) "4.4256"
   3) (integer) 4069885371962385
   4) 1) "116.40426903963089"
      2) "39.909178316988886"  
# 檢視 tour 中距離北京南站 20 km 以內的景點同時顯示其經緯度以及距離北京南站的距離(這裡的單位是 km)並且按從遠到近排序 並且顯示 geohash 的前兩個元素       
 coderknock>  GEORADIUS tour 116.385871 39.871977 20 km WITHCOORD WITHDIST DESC WITHHASH COUNT 2
1) 1) "BeijingFilmAcademy"
   2) "11.9116"
   3) (integer) 4069880657711258
   4) 1) "116.36244267225266"
      2) "39.97755242026205"
2) 1) "TianAnMen"
   2) "5.0450"
   3) (integer) 4069885555136394
   4) 1) "116.40441387891769"
      2) "39.915046196472751"
# 使用 STORE 或者 STOREDIST 不允許使用 出 COUNT 以為的其他選項       
 coderknock>  GEORADIUS tour 116.385871 39.871977 20 km WITHCOORD WITHDIST DESC WITHHASH COUNT 2 STORE posKey STOREDIST distKey
(error) ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORDS options
# 儲存
 coderknock> GEORADIUS tour 116.385871 39.871977 20 km  STORE posKey STOREDIST distKey COUNT 2
(integer) 2      
 coderknock> ZRANGE distKey 0 -1 WITHSCORES
1) "ChairmanMaoZedongMemorialHall"
2) "4.4256428962580543"
3) "TianAnMen"
4) "5.0450138622984122"
 coderknock> TYPE posKey
none
# 同時使用 STORE STOREDIST 只有最後一個選項生效
 coderknock> GEORADIUS tour 116.385871 39.871977 20 km  STORE posKey
(integer) 3
 coderknock> TYPE posKey
zset
# 這裡位置資訊儲存的是 geohash 整數
 coderknock> ZRANGE posKey 0 -1 WITHSCORES
1) "BeijingFilmAcademy"
2) "4069880657711258"
3) "ChairmanMaoZedongMemorialHall"
4) "4069885371962385"
5) "TianAnMen"
6) "4069885555136394"
# 上面儲存的位置資訊可以直接使用
 coderknock> GEORADIUS posKey 116.385871 39.871977 20 km WITHCOORD WITHDIST DESC WITHHASH COUNT 2
1) 1) "BeijingFilmAcademy"
   2) "11.9116"
   3) (integer) 4069880657711258
   4) 1) "116.36244267225266"
      2) "39.97755242026205"
2) 1) "TianAnMen"
   2) "5.0450"
   3) (integer) 4069885555136394
   4) 1) "116.40441387891769"
      2) "39.915046196472751"

GEORADIUSBYMEMBER

自3.2.0可用。

時間複雜度:O(log(N)+M), 其中 N 為指定範圍之內的元素數量, 而 M 則是被返回的元素數量。

語法:GEORADIUSBYMEMBER key member radius m|km|ft|mi[WITHCOORD][WITHDIST][WITHHASH][COUNT count][ASC|DESC][STORE key][STOREDIST key]
說明:

這個命令和 GEORADIUS 命令一樣, 都可以找出位於指定範圍內的元素, 但是 GEORADIUSBYMEMBER 的中心點是由給定的位置元素決定的, 而不是像 GEORADIUS 那樣, 使用輸入的經度和緯度來決定中心點。

關於 GEORADIUSBYMEMBER 命令的更多資訊, 請參考 GEORADIUS 命令的文件。

返回值:

一個陣列, 陣列中的每個項表示一個範圍之內的位置元素。

示例:
 coderknock>  GEORADIUSBYMEMBER tour TianAnMen  100 km
1) "XiangShan"
2) "BeijingFilmAcademy"
3) "ChairmanMaoZedongMemorialHall"
4) "TianAnMen"

刪除地理位置資訊

GEO 沒有提供刪除成員的命令,但是因為 GEO 的底層實現是 zset ,所以可以借用 ZREM 命令實現對地理位置資訊的刪除。

查詢全部 GEO

GEO 沒有提供查詢全部成員的命令,但是因為 GEO 的底層實現是 zset ,所以可以借用 ZRANGE 等命令實現對地理位置資訊的查詢。

 coderknock> ZRANGE tour 0 -1 WITHSCORES
1) "XiangShan"
2) "4069880147829102"
3) "BeijingFilmAcademy"
4) "4069880657711258"
5) "ChairmanMaoZedongMemorialHall"
6) "4069885371962385"
7) "TianAnMen"
8) "4069885555136394"

相關文章