geohash计算原理

经纬度地图:纵线是经度(-180~180)横线为纬度(-90~90)

GeoHash是一种地址编码方法。他可能把二维的空间经纬度数据编码成一个二进制字符串,而后base32后成为一个短字符串。以(123.15488794512, 39.6584212421)为例计算geohash:

1、第一步,经纬度别离计算出一个二进制数,通过二分法一直查找最小区间。

以经度123.15488794512为例,计算过程如下:

精度左边界平均值右边界后果
1-18001801
20901801
3901351800
490112.51351
5112.5123.751350
6112.5118.125123.751
7118.125120.9375123.751
8120.9375122.3438123.751
9122.3438123.0469123.751
10123.0469123.3984123.750
11123.0469123.2227123.39840
12123.0469123.1348123.22271
13123.1348123.1787123.22270
14123.1348123.1567123.17870
15123.1348123.1458123.15671
16123.1458123.1512123.15671
17123.1512123.154123.15671
18123.154123.1554123.15670
19123.154123.1547123.15541
20123.1547123.155123.15540
#经度转换后果11010111100100111010#维度转换后果10111000011001110011

2、两个二进制组合,经度占偶数位,纬度占奇数位

11100 11101 10101 01001 01100 00111 11100 01101#8个10进制数28 29 21 9 12 7 28 13wxp9d7we

3、每5位一组,进行base32编码

base32编码参照

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)}

geohash的应用

geohash的位数是9位数的时候,误差约为4米;geohash的位数是10位数的时候,误差为0.6米

geohash长度Lat位数Lon位数Lat误差Lon误差Km误差
123±23±23±2500
255± 2.8±5.6±630
378± 0.70± 0.7±78
41010± 0.087± 0.18±20
51213± 0.022± 0.022±2.4
61515± 0.0027± 0.0055±0.61
71718±0.00068±0.00068±0.076
82020±0.000086±0.000172±0.01911
92223±0.000021±0.000021±0.00478
102525±0.00000268±0.00000536±0.0005971
112728±0.00000067±0.00000067±0.0001492
123030±0.00000008±0.00000017±0.0000186

假如数据库中存储了所有用户的geohash,依据经纬度获取左近的人:

  1. 给定经纬度,计算geohash
  2. 依据半径范畴选取最小的区块,例如600m左近,能够应用6位的geohash作为最小区块
  3. 因为本身可能在最小区块内的任意地位,因而须要一并获取最小区块的四周8个邻近区块
  4. 数据库中筛选geohash的6位前缀在这9个中的所有用户,而后计算间隔,排除间隔外的用户

redis的geo命令

6个命令:

  • GEOADD 增加经纬度坐标到汇合中
  • GEODIST 获取汇合中两个成员的间隔
  • GEOHASH 获取成员的geohash
  • GEOPOS 获取汇合中成员的经纬度坐标
  • GEORADIUS 依据经纬度获取给定半径内的成员列表
  • GEORADIUSBYMEMBER 依据成员获取给定半径内的成员列表

geoadd命令:

GEOADD key longitude latitude member
  1. 操作参数是经纬度,经度范畴:-180 to 180 degrees.纬度范畴:-85.05112878 to 85.05112878 degrees.
  2. 理论存储的数据类型是zset,第四个参数member是zset的value,score是依据经纬度计算出geohash
  3. geohash的精度:52bit的长整型,计算间隔应用的公式是:Haversine
  4. 理论在redis中的数据如下图,其中score是52bit的长整型
  5. 因为理论存储的是geohash值,所以应用geopos获取的经纬度与理论保留值有肯定误差
  6. 删除应用zrem,从新geoadd会更新

左近的人查问命令:

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] #上边两个命令有store选项,会被集群当作写命令,只会在主节点执行,能够用只读模式GEORADIUS_ROGEORADIUSBYMEMBER_RO

redis内存占用状况测试

数据量内存占用
50w39.76M
100w90.21M
200w171.26M
500w484.15M
1000w907.26M