文档章节

几个地理位置信息处理方案的对比和分析

javaer
 javaer
发布于 05/11 19:45
字数 1685
阅读 41
收藏 11

对于任何LBS应用来说,让用户寻找周围的好友可能都是一个必不可少的功能,我们就以这个功能为例,来看看各种处理方案之间的差异和区别。

我们假设有如下功能需求:

显示我附近的人 由近到远排序 显示距离 2. 可能的技术方案 排除一些不通用和难以实现的技术,我们罗列出以下几种方案:

基于MySQL数据库 采用GeoHash索引,基于MySQL MySQL空间存储(MySQL Spatial Extensions) 使用MongoDB存储地理位置信息 我们一个个来分析这几种方案。

方案1:基于MySQL数据库 MySQL的使用非常简单。对于大部分已经使用MySQL的网站来说,使用这种方案没有任何迁移和部署成本。

而在MySQL中查询“最近的人”也仅需一条SQL即可,

SELECT id, ( 6371 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians ( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance FROM places HAVING distance < 25 ORDER BY distance LIMIT 0 , 100; 注:这条SQL查询的是在lat,lng这个坐标附近的目标,并且按距离正序排列,SQL中的distance单位为公里。

但使用SQL语句进行查询的缺点也显而易见,每条SQL的计算量都会非常大,性能将会是严重的问题。

先别放弃,我们尝试对这条SQL做一些优化。

可以将圆形区域抽象为正方形,如下图

根据维基百科上的球面计算公式,可以根据圆心坐标计算出正方形四个点的坐标。

然后,查询这个正方形内的目标点。

SQL语句可以简化如下:

SELECT * FROM places WHERE ((lat BETWEEN ? AND ?) AND (lng BETWEEN ? AND ?)) 这样优化后,虽然数据不完全精确,但性能提升很明显,并且可以通过给lat lng字段做索引的方式进一步加快这条SQL的查询速度。对精度有要求的应用也可以在这个结果上再进行计算,排除那些在方块范围内但不在原型范围内的数据,已达到对精度的要求。

可是这样查询出来的结果,是没有排序的,除非再进行一些SQL计算。但那又会在查询的过程中产生临时表排序,可能会造成性能问题。

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

如:wtw3ued9m就是目前我所在的位置,降低一些精度,就会是wtw3ued,再降低一些精度,就会是wtw3u。(点击链接查看坐标编码对应Google地图的位置)

所以这样一来,我们就可以在MySQL中用LIKE ‘wtw3u%’来限定区域范围查询目标点,并且可以对结果集做缓存。更不会因为微小的经纬度变化而无法用上数据库的Query Cache。

这种方案的优点显而易见,仅用一个字符串保存经纬度信息,并且精度由字符串从头到尾的长度决定,可以方便索引。

但这种方案的缺点是:从geohash的编码算法中可以看出,靠近每个方块边界两侧的点虽然十分接近,但所属的编码会完全不同。实际应用中,虽然可以通过去搜索环绕当前方块周围的8个方块来解决该问题,但一下子将原来只需要1次SQL查询变成了需要查询9次,这样不仅增大了查询量,也将原本简单的方案复杂化了。

除此之外,这个方案也无法直接得到距离,需要程序协助进行后续的排序计算。

方案3:MySQL空间存储 MySQL的空间扩展(MySQL Spatial Extensions),它允许在MySQL中直接处理、保存和分析地理位置相关的信息,看起来这是使用MySQL处理地理位置信息的“官方解决方案”。但恰恰很可惜的是:它却不支持某些最基本的地理位置操作,比如查询在半径范围内的所有数据。它甚至连两坐标点之间的距离计算方法都没有(MySQL Spatial的distance方法在5.*版本中不支持)

官方指南的做法是这样的:

GLength(LineStringFromWKB(LineString(point1, point2))) 这条语句的处理逻辑是先通过两个点产生一个LineString的类型的数据,然后调用GLength得到这个LineString的实际长度。

这么做虽然有些复杂,貌似也解决了距离计算的问题,但读者需要注意的是:这种方法计算的是欧式空间的距离,简单来说,它给出的结果是两个点在三维空间中的直线距离,不是飞机在地球上飞的那条轨迹,而是笔直穿过地球的那条直线。

所以如果你的地理位置信息是用经纬度进行存储的,你就无法简单的直接使用这种方式进行距离计算。

方案4:使用MongoDB存储地理位置信息 MongoDB原生支持地理位置索引,可以直接用于位置距离计算和查询。

另外,它也是如今最流行的NoSQL数据库之一,除了能够很好地支持地理位置计算之外,还拥有诸如面向集合存储、模式自由、高性能、支持复杂查询、支持完全索引等等特性。

对于我们的需求,在MongoDB只需一个命令即可得到所需要的结果:

db.runCommand( { geoNear: "places", near: [ 121.4905, 31.2646 ], num:100 }) 查询结果默认将会由近到远排序,而且查询结果也包含目标点对象、距离目标点的距离等信息。

由于geoNear是MongoDB原生支持的查询函数,所以性能上也做到了高度的优化,完全可以应付生产环境的压力。

方案总结 基于MongoDB做附近查询是很方便的一件事情。

MongoDB在地理位置信息方面的表现远远不限于此,它还支持更多更加方便的功能,如范围查询、距离自动计算等。

本文转载自:http://www.putaor.com/?p=586

共有 人打赏支持
上一篇: java 代理
javaer
粉丝 23
博文 64
码字总数 7899
作品 0
太原
程序员
私信 提问
结合MongoDB开发LBS应用

简介 随着近几年各类移动终端的迅速普及,基于地理位置的服务(LBS)和相关应用也越来越多,而支撑这些应用的最基础技术之一,就是基于地理位置信息的处理。我所在的项目也正从事相关系统的...

凯文加内特
2015/09/09
318
0
新增TinyMessage,并实现邮件接收处理

序言 我们在业务处理过程中,经常要处理各种信息,比如:站内信息、邮件信息、还可能有短信、彩信,甚至可能与各种IM软件进行对立的信息系统。 Tiny框架也需要面对这个问题,不一样的是我觉得...

悠悠然然
2014/04/24
0
0
20分钟轻松制作移动网站

最近关于移动网站开发或APP轻应用的内容越来越多了,,有一些好的方法可以快速开发,但不系统,这里推荐一本书吧。  PhoneGap的目的是用来快速开发移动跨平台 APP,它基于 HTML 5,支持市面...

woiwoi
2016/02/03
209
0
地理位置数据是如何被收集的?

当移动设备的GPS芯片不能接收到GPS信号时,移动设备就需要与它所连接的手机信号塔通讯和估算它与信号塔之间的距离以不断报告它的地理位置。 美国科技博客下属研究机构BI Intelligence发表了一...

oschina
2013/07/28
4.2K
7
计算机VS人脑谁具更强大问题解决能力?事实人脑胜出

     哪个更具强大的问题解决能力——大脑还是计算机?   新浪科技讯 北京时间4月18日消息,据国外媒体报道,人类大脑是非常复杂的,包含着1000 多亿个神经元,大约形成100多万亿个神经...

深度学习
04/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Accept和Content-type的意思

Accept意思是我希望接收到的数据类型 Content-type意思是我发出去的数据类型

大灰狼wow
7分钟前
0
0
Java每天10道面试题,跟我走,offer有!(五)

41.Iterator、ListIterator 和 Enumeration的区别?   迭代器是一种设计模式, 它是一个对象, 它可以遍历并选择序列中的对象, 而开发人员不需要了解 该序列的底层结构。 迭代器通常被称为...

Java干货分享
7分钟前
0
0
meta 解决页面浏览器兼容性

使用最高级的ie内核,如果支持谷歌内核,使用谷歌内核 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> 这 样写可以达到的效果是如果安装了GCF,则使用GCF来渲染页面,如...

之渊
9分钟前
0
0
极验验证demo(django+vue)

在使用之前,曾经试过用阿里云的人机验证,不过在签名部分比较复杂,下载sdk后需要自己写很多,折腾了一下,还是放弃。而腾讯云的人机验证python版本有demo,直接填写keyhe1secret就可以使用...

xiaoge2016
10分钟前
0
0
浅谈js回调

js回调极为简洁,无需声明,直接通过参数传入方法实体,调用方法实体的时候,可以直接调用方法名或者方法名加参数即可,以下看例子 socket.initWebSocket(this, userName, userId, (isSucce...

Carbenson
15分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部