热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于GeoHash算法的地理位置检索

地理位置检索服务在日常生活中随处可见,小到共享单车、高德地图,大到飞行航线轨迹。上述服务中很多相关功能都可以通过GeoHash来实现,Lu

地理位置检索服务在日常生活中随处可见,小到共享单车、高德地图,大到飞行航线轨迹。上述服务中很多相关功能都可以通过GeoHash来实现,Lucene/Solr中也有应用到GeoHash,通过GeoHash创建索引、查询索引以及距离的计算等等。

GeoHash算法本质上是空间索引的一种方式。我们可以将整个地球设想为一个二维平面,将地球上所有区域展开平铺之后通过递归分解将该平面切分为32个模块。之后再将其中的模块再进行分块,随着范围不断缩小,位置精度也越来越高。每个分块都由一定的标识来表示该块。而地球上所有经纬度坐标都会通过GeoHash算法转变为一个唯一的base32标识,该标识对应上述分块的标识,一层一层的确定分块位置,最终便可通过标识找到具体的地理位置。

首先贴出base32编码表:

对于给定的一组经纬度数据(116.389550, 39.928167),使用二分法无限逼近。对纬度39.928167进行编码:

1)区间[-90,0),[0,90],39.928167在右区间内,取1;

2)区间 [0,45),[45,90],39.928167在左区间内,取0;

3)区间 [0,22.5),[22.5,45],39.928167在右区间内,取1;

4)不断递归进行二分拆解,慢慢缩小范围(最多缩小13次)...

5)最终纬度编码为1 1 0 1 0 0 1 0 1 1 0 0 0 1 0;

对经度116.389550做上述操作,可得经度编码1 0 1 1 1 0 0 0 1 1 0 0 0 1 1。

得到经纬度的二进制位后需要对其进行合并。奇数位取纬度,偶数位取经度,每次取其二进制位的一位(不足为取0),最终合并成一个新的二进制串:

11100 11101 00100 01111 0000  01101

每5位为一个10进制数,换算过来为28、29、4、15,0,13 。通过换算过来的5个十进制数便可对照上面的base32表得到该经纬度对应的encode标识:wx4g0e。将标识编码转换为经纬度的decode算法与之相反。

经过以上过程,便可将一长串经纬度数据简化为一段短小的url标识——wx4g0e。

在得到标识之后,便可以只通过标识在地图上寻找对应的具体位置了。首先现在已分好的板块中找到W。找到W块后,再将W块进行分块,从子块之中找到X块。随后继续将X块细分,从X块的子块中找到4块。以此类推...随着不断地细分,直至找到wx4g0e对应的子块。

在该子块对应的区域内,即一定的经纬度范围内,他们的标识相同。也就是说,每一个字符串标识,都代表了而某一矩形区域。在这个矩形区域内的所有点,都共享GeoHash字符串标识,这样既可以保护隐私,又容易做缓存。

GeoHash因为是字符串查询,本身是比较耗时的。但当其做了索引之后,其查询速度是快于普通查询的,尤其是当第二次命中时查询速度比普通查询二次命中会更快。原因也很简单:单列索引、单列命中显然是高于多列的。GeoHash查询出来的仅仅是某个范围内的数据,需要对返回的数据在进行距离运算,排序后最近的便是。其优化效率主要体现在范围查询上。

由上述分析可知,GeoHash是相当有用且高效的一个算法,这个算法通过无穷的细分,能确保将每一个小块的geohash值确保在一定的范围之内,这样就为灵活的周边查找和范围查找提供了可能。

但是,GeoHash算法也存在一定的缺陷。由于GeoHash算法采用的是Peano空间填充曲线,虽然能够将将二维空间转换成一维曲线,但Peano空间填充曲线最大的缺点就是突变性,有些编码相邻但距离却相差很远,比如0111与1000,编码是相邻的,但距离相差很大。虽然geo确实尽可能的将位置相近的点hash到了一起,可是这并不是严格意义上的(实际上也并不可能,因为毕竟多一维坐标),例如在图中的红点虽然和上方的白点离的很近,可是它们的geohash值一定有差别,因为他们所属子块并不同;虽然在视野上红点和黑点离的很远,但他们因为同属一个子块,事实上geohash值相同。很多时候我们对geohash的值进行简单的排序比较,结果貌似真的能够找出相近的点,并且似乎还是按照距离的远近排列的,可是实际上会有一些点被漏掉了。

除此之外还有一个问题。就是本文在开头提到的设想,将地球展开平铺为一个平面,事实上单在二维层面,设计上这就难以做到。且由于维度越高,在地球上所属的区域面积就越小,可分的块就越少,很难做到如赤道地区的均值。

在距离计算方面,一个经度和一个纬度一起确定地球上一个地点的精确位置,相隔一经度的两点之间最短的距离随纬度的不同而变化。在相同的纬度,经度相差一度在北纬60相差的距离是在赤道相差距离的一半。对于高纬度一定距离所覆盖的经度范围要比同样距离在赤道覆盖的经度范围要大,所以可能导致相同的距离在低纬度地区和高纬度地区所对应的geohash长度不同,造成距离计算方面的误差。

眼下有一替代方案——morton码(莫顿码)代替GeoHash。针对现有剖分模型的不足,有效避免了传统经纬度格网模型在高纬度地区的形状退化和正多面体格网模型的面片形状不规则问题。通过morton码,实现了面片编码与传统地理坐标之间的转换和邻接关系的计算,弥补了上述GeoHash算法中因地球不规则性和纬度变化带来的缺陷。

Morton码可以将多维数据转化为一维数据编码,根据一维编码位数可确定多维数据保留精度,是一种比较常用的压缩编码方法,尤其是作为哈希表的映射算法等,加速了树结构数据的存储和访问速度。

Morton编码也叫z-order code ,因为其编码顺序按照空间z序,编码结果展现为一种Z形的填充曲线。

录信数据库——LSQL在其地理位置检索功能上采用了Morton编码,它实现了一维数据与二维经纬度坐标的转换。它通过交叉存储两个经纬度数据的位产生一个数即Morton码,可以应用于为一对经纬度数据产生一个唯一索引。对于经纬度坐标使用莫顿编码生成的莫顿码,可以唯一索引对应的点。这些索引为“Z”形排序。

同理,反之也可通过一个Morton编码反算出对应的一组经纬度坐标。

LSQL的这种做法和上述的GeoHash算法思想类似,都是通过对一组长经纬度坐标进行转化,转化为一个较短的编码标识。通过对该编码标识创建索引,可以极大的加快查询的性能。后续可以用于距离计算,轨迹匹配,区域碰撞等功能。但在实现原理上又与GeoHash存在本质上的不同。GeoHash通过对地球区域的分块,实现了经纬度坐标和geohash编码标识的对应,易受到地球不规则性和纬度梯度变化的影响;而LSQL的Morton编码仅仅只需通过经纬度坐标mortonhash为morton值,不受上述影响。

下面介绍一些LSQL基于morton的地理位置检索功能的使用语法样例:

1.建表语法:

create table geopoint_example(
lon y_tdouble_id,
lat y_tdouble_id,
geodata y_geopoint_idm
);

2.数据导入:

insert into table s_y_s_t_loadjson
select 'geopoint_example', 'xxx', '',CL_JSON('lon',LON,'lat',LAT,'geodata',concat(LON,',',LAT))
from (select * from geopoint) tmp;

导入数据后查询结果如图所示:

除此之外还有一些常用函数的用法,可以很方便的用于地理位置计算:

3.必备函数:

CL_GEO_DISTANCE(tmp.geodata,6.1,8.3) as distance //计算两点之间的距离
CL_GEO_UNHASH(tmp.geodata ) //用于将morton值解析出经纬度
CL_GEO_HASH(lon,lat) //用于将经纬度数据解析成morton值

由上述操作过程可了解到其使用的快捷简便。尤其是基于Morton的算法弥补了GeoHash的缺陷,还和后者拥有同样的高性能,在业内solr、ES都采用GeoHash方式的情况下是一个极大的改进。

由于LSQL基于lucene研发,自然也同原生lucene一样支持一个列存储多个值,即多值列。多值列里一般用来存储一系列同类标签信息。说到这里可能会有些小伙伴不了解多值列,多值列具体是什么呢?假设在用户画像场景下,数据里的一条记录代表一个人,里面存储了这个人的一系列行为信息,某一个多值列字段[北京 上海 杭州]就正好就可以存储这个人去旅游过的城市,多值列实现了用一个字段来存储包含多个值的属性。

但是假设某一记录存在多个多值列字段,该记录所描述的人有如下行为:在7月,北京,看球赛;在8月,上海,看电影。这两条行为就需要三个多值列[7月 8月]、[北京 上海]、[看球赛 看电影]来存储。但开源lucene能真正做到将这三个多值列关联,使多值列中存储的“7月” “北京” “看球赛”获得关联吗?答案是不能的。开源lucene目前并未实现识别多值列的之间的关联。而这也正是录信数据库LSQL后续的研发方向之一,旨在建立多值列之间的关联,实现基于多值列的在高维数据搜索与碰撞功能,以满足更高性能的数据检索和碰撞分析。

LSQL本身支持多列联合索引,正好可以将这种多列联合索引应用在多值列里,通过多列联合索引建立多个不同多值列之间的联系,并保存这种关联关系。在实现了这点之后,就可以通过多个多值列中的单个值之间的组合进行检索了,如“9月9日,在南京,吃盐水鸭”的用户。即便是“9月9日” “南京” “吃盐水鸭”这三个值存储在三个不同的多值列里,我们也能通过它们彼此之间的联系将其组合检索,最终获取到我们想要的信息。

创建多值列之间的关联的应用可不仅于此,在不同的场景下能完成不同的应用需求。如本文所探讨的地理位置检索场景。若想在该场景下进行轨迹匹配,默认的lucene只支持经纬度匹配,无法满足轨迹匹配需求。但若借助上面提到的建立多值列之前的关联,就很容易实现精准的轨迹匹配了。

一个物体的运动轨迹总是由一系列经纬度点组成,在这条轨迹上的点总是具有各自运动到该点的即刻时间和先后顺序、该点的运动方向。于是对于这三种属性,便可以用三个多值列字段来存储。一个多值列用来存储经纬度点经过mortonhash之后的morton值,一个用来存储时间先后顺序,一个用来存储运动轨迹方向。利用LSQL多列联合索引,建立这三个多值列之间的关联,实现带有运动时间先后与方向的多维度的轨迹匹配功能。不仅可以进行本文中提到的地理位置快速检索过滤,结合morton值和时间还方便进行大范围range扫描,运动的点位、时间顺序、运动方向三者结合就可以进行精确的多维轨迹匹配了。

基于多值列的在高维数据搜索与碰撞功能和带有运动时间先后与方向的多维度的轨迹匹配功能我们仍在探索,LSQL的研发之路未来可期。

LSQL不仅在地理位置检索、多值列、多列联合索引上具有亮点,其检索和统计分析服务更能够满足万亿秒查,该方面的性能也是业内的佼佼者。即席查询、模糊检索、Facet统计、列簇存储、异构存储、主从集群、过载保护、权限管理... 只需要一套数据库就可以解决大数据行业内从业人员的无数痛点。现在登陆LSQL官网:www.lucene.xin,还可以免费试用~关注录信数软官方公众号,录信团队欢迎优秀的你!

 

 

 


推荐阅读
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了新款奇骏的两个让人上瘾的功能,分别是智能互联系统和BOSE音响。通过对新款奇骏的配置和功能进行评测,探讨了这两个新增功能的使用体验和优势。此外,还介绍了新款奇骏的其他配置和改进,如增加的座椅和驾驶辅助系统,以及内饰的舒适性提升。对于喜欢音响的消费者来说,BOSE音响的升级也是一个亮点。最后,文章提到了BOSE音响的数字还原能力,以及7座版无法配备BOSE音响的原因。 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 本文详细介绍了MysqlDump和mysqldump进行全库备份的相关知识,包括备份命令的使用方法、my.cnf配置文件的设置、binlog日志的位置指定、增量恢复的方式以及适用于innodb引擎和myisam引擎的备份方法。对于需要进行数据库备份的用户来说,本文提供了一些有价值的参考内容。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • 企业数据应用挑战及元数据管理的重要性
    本文主要介绍了企业在日常经营管理过程中面临的数据应用挑战,包括数据找不到、数据读不懂、数据不可信等问题。针对这些挑战,通过元数据管理可以实现数据的可见、可懂、可用,帮助业务快速获取所需数据。文章提出了“灵魂”三问——元数据是什么、有什么用、又该怎么管,强调了元数据管理在企业数据治理中的基础和前提作用。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 本文介绍了Redis中RDB文件和AOF文件的保存和还原机制。RDB文件用于保存和还原Redis服务器所有数据库中的键值对数据,SAVE命令和BGSAVE命令分别用于阻塞服务器和由子进程执行保存操作。同时执行SAVE命令和BGSAVE命令,以及同时执行两个BGSAVE命令都会产生竞争条件。服务器会保存所有用save选项设置的保存条件,当满足任意一个保存条件时,服务器会自动执行BGSAVE命令。此外,还介绍了RDB文件和AOF文件在操作方面的冲突以及同时执行大量磁盘写入操作的不良影响。 ... [详细]
author-avatar
Luna--moon
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有