面向 LBS 应用的 GEO 数据类型

在日常生活中,我们越来越依赖搜索“附近的餐馆”、在打车软件上叫车,这些都离不开基于位置信息服务(Location-Based Service,LBS)的应用。LBS 应用访问的数据是和人或物关联的一组经纬度信息,而且要能查询相邻的经纬度范围,GEO 就非常适合应用在 LBS 服务的场景中,我们来看一下它的底层结构。

GEO 的底层结构

一般来说,在设计一个数据类型的底层结构时,我们首先需要知道,要处理的数据有什么访问特点。所以,我们需要先搞清楚位置信息到底是怎么被存取的。

把不同车辆的 ID 和它们对应的经纬度信息存在 Hash 集合中:

一旦涉及到范围查询,就意味着集合中的元素需要有序,但 Hash 类型的元素是无序的

Sorted Set 类型也支持一个 key 对应一个 value 的记录模式,其中,key 就是 Sorted Set 中的元素,而 value 则是元素的权重分数。更重要的是,Sorted Set 可以根据元素的权重分数排序,支持范围查询。

GEO 类型的底层数据结构就是用 Sorted Set 来实现的

用 Sorted Set 来保存车辆的经纬度信息时,Sorted Set 的元素是车辆 ID,元素的权重分数是经纬度信息,如下图所示:

这时问题来了,Sorted Set 元素的权重分数是一个浮点数(float 类型),而一组经纬度包含的是经度和纬度两个值,是没法直接保存为一个浮点数的,那具体该怎么进行保存呢?

这就要用到 GEO 类型中的 GeoHash 编码了。

GeoHash 的编码方法

为了能高效地对经纬度进行比较,Redis 采用了业界广泛使用的 GeoHash 编码方法,这个方法的基本原理就是“二分区间,区间编码”。

当我们要对一组经纬度进行 GeoHash 编码时,我们要先对经度和纬度分别编码,然后再把经纬度各自的编码组合成一个最终编码。

做完 5 次分区后,我们把经度值 116.37 定位在[112.5, 123.75]这个区间,并且得到了经度值的 5 位编码值,即 11010。这个编码过程如下表所示:

对纬度的编码方式,和对经度的一样,只是纬度的范围是[-90,90],下面这张表显示了对纬度值 39.86 的编码过程。

当一组经纬度值都编完码后,我们再把它们的各自编码值组合在一起,组合的规则是:最终编码值的偶数位上依次是经度的编码值,奇数位上依次是纬度的编码值,其中,偶数位从 0 开始,奇数位从 1 开始。

这 4 个分区对应了 4 个方格,每个方格覆盖了一定范围内的经纬度值,分区越多,每个方格能覆盖到的地理空间就越小,也就越精准。我们把所有方格的编码值映射到一维空间时,相邻方格的 GeoHash 编码值基本也是接近的,如下图所示:

所以,我们使用 Sorted Set 范围查询得到的相近编码值,在实际的地理空间上,也是相邻的方格,这就可以实现 LBS 应用“搜索附近的人或物”的功能了。

不过,我要提醒你一句,有的编码值虽然在大小上接近,但实际对应的方格却距离比较远。例如,我们用 4 位来做 GeoHash 编码,把经度区间[-180,180]和纬度区间[-90,90]各分成了 4 个分区,一共 16 个分区,对应了 16 个方格。编码值为 0111 和 1000 的两个方格就离得比较远,如下图所示:

如何操作 GEO 类型?

  • GEOADD: 添加
  • GEORADIUS: 查找一定范围内的元素
# 顺序:                经度        维度      标记GEOADD cars:locations 116.034579 39.030452 33
GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10

如何自定义数据类型?

RedisObject

Redis 的基本对象结构

组成:

  1. type:表示值的类型,涵盖了我们前面学习的五大基本类型;
  2. encoding:是值的编码方式,用来表示 Redis 中实现各个基本类型的底层数据结构,例如 SDS、压缩列表、哈希表、跳表等;
  3. lru:记录了这个对象最后一次被访问的时间,用于淘汰过期的键值对;
  4. refcount:记录了对象的引用计数;
  5. *ptr:是指向数据的指针。

我们在定义了新的数据类型后,也只要在 RedisObject 中设置好新类型的 typeencoding,再用*ptr指向新类型的实现,就行了。

开发一个新的数据类型

首先,我们需要为新数据类型定义好它的底层结构、type 和 encoding 属性值,然后再实现新数据类型的创建、释放函数和基本命令。

第一步:定义新数据类型的底层结构

newtype.h:

struct NewTypeObject {    struct NewTypeNode *head;     size_t len; }NewTypeObject;

NewTypeNode: 自定义的新类型的底层结构

struct NewTypeNode {    long value;    struct NewTypeNode *next;};

第二步:在 RedisObject 的 type 属性中,增加这个新类型的定义

定义是在 Redis 的 server.h 文件中, 增加一个叫作 OBJ_NEWTYPE 的宏定义,用来在代码中指代 NewTypeObject 这个新类型

#define OBJ_STRING 0    /* String object. */#define OBJ_LIST 1      /* List object. */#define OBJ_SET 2       /* Set object. */#define OBJ_ZSET 3      /* Sorted set object. */…#define OBJ_NEWTYPE 7

第三步:开发新类型的创建和释放函数

Redis 把数据类型的创建和释放函数都定义在了 object.c 文件中。所以,我们可以在这个文件中增加 NewTypeObject 的创建函数 createNewTypeObject,如下所示:

robj *createNewTypeObject(void){   NewTypeObject *h = newtypeNew();    robj *o = createObject(OBJ_NEWTYPE,h);   return o;}

newtypeNew 函数: 为新数据类型初始化内存结构的

NewTypeObject *newtypeNew(void){    NewTypeObject *n = zmalloc(sizeof(*n));    n->head = NULL;    n->len = 0;    return n;}

createObject: 是 Redis 本身提供的 RedisObject 创建函数,它的参数是数据类型的 type 和指向数据类型实现的指针*ptr。

robj *createObject(int type, void *ptr) {    robj *o = zmalloc(sizeof(*o));    o->type = type;    o->ptr = ptr;    ...    return o;}

第四步:开发新类型的命令操作

分三小步:

  1. 在 t_newtype.c 文件中增加命令操作的实现。比如说,我们定义 ntinsertCommand 函数,由它实现对 NewTypeObject 单向链表的插入操作:
void ntinsertCommand(client *c){  //基于客户端传递的参数,实现在NewTypeObject链表头插入元素}
  1. 在 server.h 文件中,声明我们已经实现的命令,以便在 server.c 文件引用这个命令,例如:
void ntinsertCommand(client *c)
  1. 在 server.c 文件中的 redisCommandTable 里面,把新增命令和实现函数关联起来。例如,新增的 ntinsert 命令由 ntinsertCommand 函数实现,我们就可以用 ntinsert 命令给 NewTypeObject 数据类型插入元素了。
struct redisCommand redisCommandTable[] = { ...{"ntinsert",ntinsertCommand,2,"m",...}}

宏定义中有浮点数_GEO是什么?还可以定义新的数据类型吗?相关推荐

  1. lmi克罗内克积 matlab_LMI中有关于克罗内克积的决策变量,如何定义?

    本帖最后由 红色独孤客 于 2016-5-23 14:20 编辑 LMI中有关于克罗内克积的决策变量,如何定义? 要求该LMIx小于零的解,其中,A为3x3, IN为5x5单位矩阵,B=[1,0,0] ...

  2. Go 学习笔记(27)— type 关键字(类型定义、类型别名、类型查询、定义接口、定义结构体)

    1. 类型别名定义 定义类型别名的写法为: type TypeAlias = Type 类型别名规定: TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类 ...

  3. LDF转Excel;LDF转位定义;Excel转LDF;Excel转位定义;MatrixCreat(一)之LIN矩阵详解

    LIN矩阵详解 一.摘要 1.描述 2.关键字 二.什么是LIN 1.发展历史 2.子网与节点 3.主/从机节点与主/从机任务 4.特点 5.帧的结构 6.调度表 三.LIN矩阵 1.矩阵表 2.报文 ...

  4. 在c语言中函数的定义变量的值为,变量定义(C语言中变量的声明和定义)

    变量定义(C语言中变量的声明和定义),哪吒游戏网给大家带来详细的变量定义(C语言中变量的声明和定义)介绍,大家可以阅读一下,希望这篇变量定义(C语言中变量的声明和定义)可以给你带来参考价值. 3.函数 ...

  5. python函数定义语句可执行_python学习笔记-定义函数

    python中定义函数的关键词是def,例如定义一个叫my_function的函数我们可以这么定义, 其中括号内的x,y为传入的参数. def my_function(): # function bo ...

  6. MySQL中定义fk语句_MySQL的SQL语句 - 数据定义语句(6)- ALTER TABLE 语句 (4)

    外键和其他约束 InnoDB和NDB存储引擎支持 FOREIGN KEY 和 REFERENCES 子句,这些存储引擎实现了ADD [CONSTRAINT [symbol]] FOREIGN KEY ...

  7. python在类中定义全局变量_python程序中用类变量代替global 定义全局变量

    在python编程中,一般使用global 关键字来定义全局变量,但是发现 global 关键字在涉及多个文件时,好像存在问题. 比如,单个文件下用global定义使用全局变量的情况 ,看下面的代码 ...

  8. YDOOK:ANSYS 定义材料属性步骤与流程 ANSYS 如何定义材料属性的详细教程

    1. 使用 ANSYS 仿真的时候为什么要进行材料特性的定义? >> 在分析物理几何模型的时候,对于不同的材质区域需要定义不同的材料区域.对于电磁场仿真而言,常用的需要定义材料特性的区域有 ...

  9. 【C 语言】结构体 ( 结构体类型定义 | 结构体类型别名 | 声明结构体变量的三种方法 | 栈内存中声明结构体变量 | 定义隐式结构体时声明变量 | 定义普通结构体时声明变量 )

    文章目录 一.结构体类型定义 二.结构体类型别名 三.结构体类型变量声明 1.使用结构体类型 ( 别名 ) 声明变量 2. 定义隐式结构体时声明变量 3.定义普通结构体时声明变量 二.完整代码示例 一 ...

最新文章

  1. 使用jquery进行多行表格数据验证
  2. linux epoll 开发指南-【ffrpc源码解析】
  3. WinCE驱动的动态卸载
  4. 2017.4.19 多项式输出 思考记录
  5. CSS-解决inline-block空隙的最好方法
  6. Sass 内置函数总结
  7. Eaplayer漂亮好用的wordpress音乐播放器插件
  8. sql基础语句(联表查询)
  9. visio制作算法流程图
  10. Linux打包与压缩命令
  11. 网页繁体和简体转换js插件
  12. 炒股的10个境界,你在第几级?
  13. 32:汉字表示的大写数字金额
  14. 我很喜欢研究这些比较酷的效果
  15. 无盘服务器配置网众,三种网众无盘常见服务器配置推荐
  16. iCAN: 面向HOI检测的以实例为中心的注意力网络
  17. 最新调研-化妆品和个人护理乳化剂行业研究分析报告
  18. CM开机动画 BootanimationMikuSony
  19. Aerospike学习
  20. python腾讯课堂自动签到_网课终结者——基于auto.js的腾讯课堂,雨课堂等各大网课自动签到以及auto.js新手教程...

热门文章

  1. css 文本溢出 0302
  2. django-模型类操作-初期阶段-小结
  3. javascript-布尔-空与未定义-查看数据类型
  4. Laravel 5.8 正式发布(文档翻译已启动)
  5. python 3.5 django 笔记(六)修改博客标题与内容
  6. Android性能优化之内存篇
  7. 《每周一点canvas动画》——圆周运动
  8. 微软职位内部推荐-Senior Software Engineer_HPC
  9. 求带权中位数的Select算法
  10. qt lighthouse相关