基于Redis的GeoHash在PHP上的应用
GeoHash有着以下几个特点
- GeoHash用一个字符串表示经度和纬度两个坐标,便于加上索引.
- GeoHash表示的并不是一个点,而是一个矩形区域.
- 编码的前缀可以表示更大的区域, 可以很方便的进行区域检索, 聚合等相关操作.
综上所述, GeoHash比直接用经纬度的高效很多.
➡️GeoHash算法
编码
GeoHash首先将精度范围设定为划分成两个区间(-180, 0), (0, 180), 如果经度坐标落到了前一个区间则标记为0, 反之则标记为1.
然后再将上一步获得到的区间划分成两个区间, 重复上一步.
重复前前两步直到得到自己所需要的精度
以此类推来得到纬度的HashBin (关于纬度的初始划分区间, 一般的共用算法里都是±90, 而redis里用的却是±85.05112878)
如 (23.17015353059966287, 113.46623629331588745) 通过区间划分可以得到:
(1101000010101111111001011110, 101000101101111011011100011)接下来是对划分结果的合并, 按照
奇数位是纬度, 偶数位为经度
的原则来进行合并.
上面的例子合并可得:
1110011000000100110110011111111011111001011100101011110之后再按照非标准的base32算法进行字符串的转化, 下面是对照表格
十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
二进制 | 00000 | 00001 | 00010 | 00011 | 00100 | 00101 | 00110 | 00111 | 01000 | 01001 | 01010 | 01011 | 01100 | 01101 | 01110 | 01111 |
base32 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | b | c | d | e | f | g |
十进制 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
二进制 | 10000 | 10001 | 10010 | 10011 | 10100 | 10101 | 10110 | 10111 | 11000 | 11001 | 11010 | 11011 | 11100 | 11101 | 11110 | 11111 |
base32 | h | j | k | m | n | p | q | r | s | t | u | v | w | x | y | z |
最终(23.17015353059966287, 113.46623629331588745)被转化成’ws2emzrtfby’.
解码则和编码正好相反即可.
➡️PHP的实现
1 | class Geohash |
➡️Redis上的应用
Redis的GEO特性在Redis 3.2
版本释出,这个功能可以将用户给定的地理位置信息储存起来,并对这些信息进行操作.
GEOADD
1 | GEOADD key longitude latitude name [longitude latitude name ...] |
GEOADD 命令每次可以添加一个或多个经纬度地理位置。 其中key
为储存地理位置的集合, 而 longitude
、 latitude
和 name
则分别为地理位置的经度、纬度、名字.
1 | 127.0.0.1:6379> GEOADD geo_test 113.46623629331588745 23.17015353059966287 test_1 113.46620947122573853 23.17010790561879929 test_2 |
GEOPOS
GEOPOS
命令可以获取Geo的坐标
1 | GEOPOS key name [name ...] |
如:
1 | 127.0.0.1:6379> geopos geo_test test_2 |
GEODIST
计算两个坐标点之间的距离
1 | GEODIST key location-x location-y [unit] |
其中unit
表示单位 默认为 m
m
表示单位为米。km
表示单位为千米。mi
表示单位为英里。ft
表示单位为英尺。
GEORADIUS
和 GEORADIUSBYMEMBER
这两个命令都是获取一定范围内的坐标
1 | GEORADIUS key longitude latitude radius m|km|ft|mi[WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key] |
WITHCOORD
在返回匹配的位置时会将位置的经纬度一并返回.WITHDIST
指定中心返回项目的距离, 距离以与指定为命令的radius参数的单位相同的单位返回.WITHHASH
还以52位无符号整数的形式返回项的原始geohash编码的有序集分数ASC|DESC
ASC
可以让查找结果根据距离从近到远排序, 而DESC
则可以让查找结果根据从远到近排序COUNT
指定要返回的结果数量STORE key
结果存到新的有序集合中, 以GeoHash
值做score
, 该选项与WITH[DIST|COORD|HASH]
选项冲突STOREDIST key
结果存到新的有序集合中, 以与指定位置的距离作score
, 该选项与WITH[DIST|COORD|HASH]
选项冲突
1 | 127.0.0.1:6379> georadius key 113.46620947122573853 23.17010790561879929 100 km WITHCOORD WITHDIST |
GEOHASH
将redis里的Geo
转化为标准的GeoHash
输出
注意:这里输出的是标准的GeoHash
, 不是redis里面存储的HashInt
转化出来的
1 | GEOHASH key member [member ...] |
1 | 127.0.0.1:6379> geohash geo_test test_1 test_2 |
➡️存储原理
Redis
里的Geo
坐标在实际存储时是将gps
坐标分别按照(-180, 180)和(-85.05112878, 85.05112878)来划分计算成HashInt
作为zset集合
进行存储的.
所以Redis
的Geo
操作中并没有删除命令, 可以直接使用zrem
命令去删除Geo
坐标.