Geohash principle and redis geo related operations

Time:2021-6-14

Principle of geohash calculation

Longitude and latitude map: longitudinal line is longitude (- 180 ~ 180), horizontal line is latitude (- 90 ~ 90)

Geohash principle and redis geo related operations

Geohash is an address coding method. It can encode the two-dimensional spatial longitude and latitude data into a binary string, and then base32 becomes a short string. With(123.15488794512, 39.6584212421)As an example, geohash is calculated

1. The first step is to calculate a binary number of longitude and latitude, and constantly find the minimum interval through dichotomy.

In longitude123.15488794512For example, the calculation process is as follows:

accuracy Left border average value Right border result
1 -180 0 180 1
2 0 90 180 1
3 90 135 180 0
4 90 112.5 135 1
5 112.5 123.75 135 0
6 112.5 118.125 123.75 1
7 118.125 120.9375 123.75 1
8 120.9375 122.3438 123.75 1
9 122.3438 123.0469 123.75 1
10 123.0469 123.3984 123.75 0
11 123.0469 123.2227 123.3984 0
12 123.0469 123.1348 123.2227 1
13 123.1348 123.1787 123.2227 0
14 123.1348 123.1567 123.1787 0
15 123.1348 123.1458 123.1567 1
16 123.1458 123.1512 123.1567 1
17 123.1512 123.154 123.1567 1
18 123.154 123.1554 123.1567 0
19 123.154 123.1547 123.1554 1
20 123.1547 123.155 123.1554 0
#Longitude conversion results
11010111100100111010

#Dimension conversion results
10111000011001110011

2. Two binary combinations, longitude in even digits and latitude in odd digits

11100 11101 10101 01001 01100 00111 11100 01101
#Eight decimal numbers
28 29 21 9 12 7 28 13
wxp9d7we

3. Each group of 5 bits is coded with base32

Base32 encoding reference

Geohash principle and redis geo related operations

Geohash principle and redis geo related operations

public static function geoHash($lon, $lat, $precision = 10) {
    $lonA = '';
    $s = -180;$t = 180;
    $totalBits = $precision * 5;
    $bits = ceil($totalBits / 2);
    for ($i = 0; $i < $bits; $i++) {
        $mid = ($s + $t) / 2;
        if ($lon >= $mid) {
            $lonA .= 1;
            $s = $mid;
        } else {
            $t = $mid;
            $lonA .= 0;
        }
    }
    $latA = '';
    $s = -90;$t = 90;
    $bits = floor($totalBits / 2);
    for ($i = 0; $i < $bits; $i++) {
        $mid = ($s + $t) / 2;
        if ($lat >= $mid) {
            $latA .= 1;
            $s = $mid;
        } else {
            $t = $mid;
            $latA .= 0;
        }
    }
    $geoBinary = '';
    for ($i = 0; $i < $bits; $i++) {
        $geoBinary .= $lonA[$i] . $latA[$i];
    }
    return self::base32Encode($geoBinary, $totalBits);
}
 
public static function decodeGeoHash(string $geohash) {
    $geoBinary = self::base32Decode($geohash);
    $lonS = -180;$lonT = 180;
    $latS = -90;$latT = 90;
    for ($i = 0; $i < strlen($geoBinary); $i += 2) {
        $lonCode = $geoBinary[$i];
        $lonMid = ($lonS + $lonT) / 2;
        if ($lonCode) {
            $lonS = $lonMid;
        } else {
            $lonT = $lonMid;
        }
        $latCode = $geoBinary[$i + 1];
        $latMid = ($latS + $latT) / 2;
        if ($latCode) {
            $latS = $latMid;
        } else {
            $latT = $latMid;
        }
    }
    $geo = [($lonS + $lonT) / 2, ($latS + $latT) / 2];
    return $geo;
}
 
public static function base32Encode(string $geoBinary, $bits)
{
    $encodeMap = '0123456789bcdefghjkmnpqrstuvwxyz';
    $encode = '';
    for ($i = 0; $i < $bits; $i += 5) {
        $digit = intval(substr($geoBinary, $i, 5), 2);
        $encode .= $encodeMap[$digit];
    }
    return $encode;
}
 
public static function base32Decode(string $geoHash)
{
    $encodeMap = '0123456789bcdefghjkmnpqrstuvwxyz';
    $decode = '';
    for ($i = 0; $i < strlen($geoHash); $i++) {
        $digit = strpos($encodeMap, $geoHash[$i]);
        $binary = base_convert($digit, 10, 2);
        $decode .= sprintf('%05d', $binary);
    }
    return $decode;
}
 
public function testGeoHash()
{
    $geohash = self::geoHash(123.15488794512, 39.6584212421, 10);//wxp9d7wehc
    $geo = self::decodeGeoHash($geohash);// (123.15488755703, 39.658420979977)
}

The use of geohash

When the number of geohash digits is 9, the error is about 4 meters; When the number of geohash digits is 10, the error is 0.6 meters

Geohash length Number of LAT digits Lon digits Lat error Lon error Km error
1 2 3 ±23 ±23 ±2500
2 5 5 ± 2.8 ±5.6 ±630
3 7 8 ± 0.70 ± 0.7 ±78
4 10 10 ± 0.087 ± 0.18 ±20
5 12 13 ± 0.022 ± 0.022 ±2.4
6 15 15 ± 0.0027 ± 0.0055 ±0.61
7 17 18 ±0.00068 ±0.00068 ±0.076
8 20 20 ±0.000086 ±0.000172 ±0.01911
9 22 23 ±0.000021 ±0.000021 ±0.00478
10 25 25 ±0.00000268 ±0.00000536 ±0.0005971
11 27 28 ±0.00000067 ±0.00000067 ±0.0001492
12 30 30 ±0.00000008 ±0.00000017 ±0.0000186

Suppose that the geohash of all users is stored in the database, and people nearby are obtained according to longitude and latitude

  1. Given latitude and longitude, geohash is calculated
  2. Select the smallest block according to the radius range, for example, around 600m, and use 6-bit geohash as the smallest block
  3. Since it may be in any position within the smallest block, it is necessary to obtain 8 adjacent blocks around the smallest block
  4. In the database, all the users with the 6-bit prefix of geohash in these 9 users are filtered, and then the distance is calculated to exclude the users beyond the distance

Geohash principle and redis geo related operations

Redis’s geo command

Six commands:

  • GEOADDAdd latitude and longitude coordinates to the set
  • GEODISTGets the distance between two members in a collection
  • GEOHASHGets the geohash of the member
  • GEOPOSGets the longitude and latitude coordinates of the members in the set
  • GEORADIUSAccording to latitude and longitude, get the member list within the given radius
  • GEORADIUSBYMEMBERGet the list of members within the given radius according to the members

Geoadd command:

GEOADD key longitude latitude member
  1. The operation parameters are longitude and latitude, longitude range: – 180 to 180 degrees. Latitude range: – 85.05112878 to 85.05112878 degrees
  2. The actual stored data type is Zset, the fourth parameter member is the value of Zset, and score is the geohash calculated according to latitude and longitude
  3. Geohash precision: 52bit long integer, the formula used to calculate the distance is: haveline
  4. The actual data in redis is shown in the figure below, where score is a 52 bit long integer
  5. Because the geohash value is actually stored, there is a certain error between the longitude and latitude obtained by using geopos and the actual saved value
  6. Delete the use of zrem, re geoadd will update

Geohash principle and redis geo related operations

Nearby people query command:

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
 
#The above two commands have the store option, and will be used as write commands by the cluster. They will only be executed at the primary node and can be read-only
GEORADIUS_RO
GEORADIUSBYMEMBER_RO

Redis memory usage test

Data volume Memory footprint
50w 39.76M
100w 90.21M
200w 171.26M
500w 484.15M
1000w 907.26M