阿里云Redis GEO地理位置功能上线啦

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
简介: Redis 3.2版本一个重大的更新是新增了GEO地理位置相关的命令。 ApsaraDB for Redis对地理位置的支持对应的版本也已经发布了,目前可以通过提工单升级版本来支持。

Redis 3.2版本一个重大的更新是新增了GEO地理位置相关的命令。
ApsaraDB for Redis对地理位置的支持对应的版本也已经发布了,目前可以通过提工单升级版本来支持。
目前Redis对地理位置支持提供了一下6个命令:

  1. geoadd: 增加地理位置的坐标。
  2. geodist: 获取两个地理位置的距离。
  3. geohash: 获取地理位置的GeoHash值。
  4. geopos: 获取地理位置的坐标。
  5. georadius: 根据给定经纬度坐标获取指定范围内的地理位置集合。
  6. georadiusbymember: 根据给定地理位置获取指定范围内的地理位置集合。

GeoHash基本原理

GeoHash是一种地址编码,通过切分地图区域为小方块(切分次数越多,精度越高),它能把二维的经纬度编码成一维的字符串。也就是说,理论上geohash字符串表示的并不是一个点,而是一个矩形区域,只要矩形区域足够小,达到所需精度即可。

编码过程

以经纬度(116.3906,39.92324)为例:

  1. 对于维度39.92324, 39.92324属于(0, 90),所以取编码为1。然后再将(0, 90)分成 (0, 45), (45, 90)两个区间,而39.92324位于(0, 45),所以编码为0。以此类推,直到精度符合要求为止,得到纬度编码为1011 1000 1100 0111 1001。
  2. 经度也用同样的算法,对(-180, 180)依次细分,得到116.3906的编码为1101 0010 1100 0100 0100。
  3. 接下来将经度和纬度的编码合并,奇数位是纬度,偶数位是经度,得到编码 11100 11101 00100 01111 00000 01101 01011 00001。
  4. 最后,用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,得到(39.92324, 116.3906)的编码为wx4g0ec1。

(116.3906,39.92324)对应的地图位置就是http://geohash.org/wx4g0ec1经纬度为,降低一些精度,就会是http://geohash.org/wx4g0ec,再降低一些精度,就会是http://geohash.org/wx4g0

编码特性

不难看出这样的编码方式仅用一个字符串保存经纬度信息,并且精度由字符串从头到尾的长度决定,编码长度越长,精度越高。GeoHash值的前缀相同的位数越多,代表的位置越接近,可以方便索引。(反之不成立,位置接近的GeoHash值不一定相似).
但这种方案的缺点是:从geohash的编码算法中可以看出,靠近每个方块边界两侧的点虽然十分接近,但所属的编码会完全不同。实际应用中,需要通过去搜索环绕当前方块周围的8个方块来解决该问题。
除此之外,这个方案也无法直接得到距离,需要程序协助进行后续的排序计算。

具体的可以参考一下几个文档:

Redis Geo命令实现

Redis将地理位置的52位GeoHash值作为有序集合的score,将地理位置存放在有序集合中进行保存。后续按位置搜索时,依据GeoHash的特性搜索当前方块与环绕当前方块的8个方块来搜索目标位置集合。

GEOADD

增加地理位置坐标,命令格式如下:

GEOADD key longitude latitude member [longitude latitude member ...]
AI 代码解读

Redis中接受的有效的精度范围为-180到180度,有效维度范围为-85.05112878到 85.05112878度(靠近南北极的一小块地方是无法生成索引的)。

实现方式:

Redis内部使用有序集合来保存key,每一个member的score大小为一个52位的Geohash值(double类型精度为52位)。
实际上Redis内部实现的时候就是将GEOADD命令转换成ZADD命令来实现的。(这也解释了为什么没有专门的georem命令,地理位置信息是通过使用ZREM命令来删除成员。)
GEOADD命令的实现如下

void geoaddCommand(client *c) {
    ...
    int elements = (c->argc - 2) / 3;
    int argc = 2+elements*2; /* ZADD key score ele ... */
    robj **argv = zcalloc(argc*sizeof(robj*));
    argv[0] = createRawStringObject("zadd",4);
    argv[1] = c->argv[1]; /* key */
    incrRefCount(argv[1]);

    /* Create the argument vector to call ZADD in order to add all
     * the score,value pairs to the requested zset, where score is actually
     * an encoded version of lat,long. */
    int i;
    for (i = 0; i < elements; i++) {
        double xy[2];

        if (extractLongLatOrReply(c, (c->argv+2)+(i*3),xy) == C_ERR) {
            for (i = 0; i < argc; i++)
                if (argv[i]) decrRefCount(argv[i]);
            zfree(argv);
            return;
        }

        /* Turn the coordinates into the score of the element. */
        GeoHashBits hash;
        geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
        GeoHashFix52Bits bits = geohashAlign52Bits(hash);
        robj *score = createObject(OBJ_STRING, sdsfromlonglong(bits));
        robj *val = c->argv[2 + i * 3 + 2];
        argv[2+i*2] = score;
        argv[3+i*2] = val;
        incrRefCount(val);
    }

    /* Finally call ZADD that will do the work for us. */
    replaceClientCommandVector(c,argc,argv);
    zaddCommand(c);
}
AI 代码解读

例子

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEODIST Sicily Palermo Catania
"166274.15156960039"
redis> GEORADIUS Sicily 15 37 100 km
1) "Catania"
redis> GEORADIUS Sicily 15 37 200 km
1) "Palermo"
2) "Catania"
AI 代码解读

GEODIST

返回两点间距离,命令格式如下

GEODIST key member1 member2 [unit]
AI 代码解读

单位可选项为m(米,默认值), km(千米),mi(英里),ft(英尺)。
返回double值,若有member不存在,则返回NULL.

实现方式:

使用WGS84坐标系统,计算距离时使用Haversine公式。由于地球并不是严格标准的,计算出来的距离有最大约0.5%的误差。

例子:

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEODIST Sicily Palermo Catania
"166274.15156960039"
redis> GEODIST Sicily Palermo Catania km
"166.27415156960038"
redis> GEODIST Sicily Palermo Catania mi
"103.31822459492736"
redis> GEODIST Sicily Foo Bar
(nil)
AI 代码解读

GEOHASH

返回key中对应成员的geohash值。命令格式如下:

GEOHASH key member [member ...]
AI 代码解读

实现方式:

Redis在内部生成有序集合成员score时的geohash值与标准的算法略有差异(Redis内部使用-85,85作为维度范围,标准使用-90,90)。
这个命令返回的是标准值,与https://en.wikipedia.org/wiki/Geohash中标准算法和geohash.org网站的结果一致。代码如下:

/* Get Score */
zsetScore(zobj, c->argv[j], &score);

/* The internal format we use for geocoding is a bit different
 * than the standard, since we use as initial latitude range
 * -85,85, while the normal geohashing algorithm uses -90,90.
 * So we have to decode our position and re-encode using the
 * standard ranges in order to output a valid geohash string. */

/* Decode... */
double xy[2];
if (!decodeGeohash(score,xy)) {
    addReply(c,shared.nullbulk);
    continue;
}

/* Re-encode */
GeoHashRange r[2];
GeoHashBits hash;
r[0].min = -180;
r[0].max = 180;
r[1].min = -90;
r[1].max = 90;
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
AI 代码解读

例子

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEOHASH Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"
AI 代码解读

GEOPOS

获取地理位置的经纬度坐标,命令格式如下:

GEOPOS key member [member ...]
AI 代码解读

经纬度坐标是被转成52位的GeoHash保存起来的,返回的时候重新解码成经纬度坐标。由于精度问题,返回值可能与设置的值略有差异。

例子

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEOPOS Sicily Palermo Catania NonExisting
1) 1) "13.361389338970184"
   2) "38.115556395496299"
2) 1) "15.087267458438873"
   2) "37.50266842333162"
3) (nil)
AI 代码解读

GEORADIUS, GEORADIUSBYMEMBER

获取指定范围内的地理位置集合,命令格式如下:

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]
AI 代码解读

GEORADIUS与GEORADIUSBYMEMBER,前一个是获取任意经纬度周围的地理集合,后一个是获取某个地理位置周围的地理位置集合。它们的内部实现和可选参数是一致的。
可选项:
WITHDIST: 同时返回地理位置与给定位置的距离
WITHCOORD: 同时返回地理位置的经纬度坐标
WITHHASH: 同时返回Redis内部的GeoHash值(非标准算法值),一般用于debug
ASC|DESC:结果按距离升降序排序
STORE|STOREDIST: 结果存到新的有序集合中,前者以GeoHash值做score,后者以与指定位置的距离作score,该选项与WITH[DIST|COORD|HASH]选项冲突

实现方式:

GeoHash值的前缀相同的位数越多,代表的位置越接近,可以方便索引。但反之不成立,位置接近的GeoHash值不一定相似。靠近每个方块边界两侧的点虽然十分接近,但所属的编码会完全不同。实际应用中,需要通过去搜索环绕当前方块周围的8个方块来解决该问题。
搜索的时候会检查挡墙方块+8个覆盖整个搜索半径的区域,不断的去除geohash的低位,直到这9个方块能覆盖搜索半径位置。再一次搜索计算每个位置的距离。

例子:

GEORADIUS:

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
   2) "190.4424"
2) 1) "Catania"
   2) "56.4413"
redis> GEORADIUS Sicily 15 37 200 km WITHCOORD
1) 1) "Palermo"
   2) 1) "13.361389338970184"
      2) "38.115556395496299"
2) 1) "Catania"
   2) 1) "15.087267458438873"
      2) "37.50266842333162"
redis> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
   2) "190.4424"
   3) 1) "13.361389338970184"
      2) "38.115556395496299"
2) 1) "Catania"
   2) "56.4413"
   3) 1) "15.087267458438873"
      2) "37.50266842333162"
AI 代码解读

GEORADIUSBYMEMBER:

redis> GEOADD Sicily 13.583333 37.316667 "Agrigento"
(integer) 1
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEORADIUSBYMEMBER Sicily Agrigento 100 km
1) "Agrigento"
2) "Palermo"
AI 代码解读
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
怀听
+关注
目录
打赏
0
0
0
3
9159
分享
相关文章
Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
通过本文的介绍,我们详细讲解了 Lua 脚本在 Redis 中的作用、`eval` 命令的使用方法以及 `redis.call` 和 `redis.pcall` 的区别和用法。通过合理使用 Lua 脚本,可以实现复杂的业务逻辑,确保操作的原子性,并减少网络开销,从而提高系统的性能和可靠性。
88 13
如何用Redis高效实现点赞功能?用Set?还是Bitmap?
在众多软件应用中,点赞功能几乎成为标配。本文从实际需求出发,探讨如何利用 Redis 的 `Set` 和 `Bitmap` 数据结构设计高效点赞系统,分析其优缺点,并提供 PHP 实现示例。通过对比两种方案,帮助开发者选择最适合的存储方式。
95 3
Redis GEO
10月更文挑战第19天
76 1
大数据-43 Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
大数据-43 Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
66 2
Redis Geo:解锁地理位置数据的新可能性
Redis Geo:解锁地理位置数据的新可能性
277 0
Redis源码剖析之GEO——Redis是如何高效检索地理位置的?
Redis源码剖析之GEO——Redis是如何高效检索地理位置的?
207 0
PHP使用Redis的GEO(地理位置)命令
Redis自3.2版本开始新增对GEO(地理位置)的支持,但是地球不是一个完美的球体,在最坏的情况下的偏差可能是0.5%。接近两极的区域是不支持的,支持坐标的有效的经度从-180度到180度;有效的纬度从-85.05112878度到85.05112878度,超出范围将会报错。
1436 0

相关产品

  • 云数据库 Tair(兼容 Redis)
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等