方案一: Mysql
通过球面两点距离公式
$ AB = R * \arccos{( \cos{wA} * \cos{wB} * \cos{jB-jA} + \sin{wA}*\sin{wB})}$
其中 A(jA,wA)和B(jB,wB),推导过程见这里
对于大部分已经使用MySQL的应用来说,使用这种方案没有任何迁移和部署成本,但使用SQL语句进行查询的缺点也显而易见,每条SQL的计算量都会非常大,性能将会是严重的问题。
1 | # 创建表 |
通过空间存储(spatial)
MySQL的空间扩展(MySQL Spatial Extensions),它允许在MySQL中直接处理、保存和分析地理位置相关的信息,但是需要手动排序(请忽略distance字段)。文档可见这里
1 | # 创建表 |
通过Geohash算法
GeoHash是一种地址编码,通过切分地图区域为小方块(切分次数越多,精度越高),它能把二维的经纬度编码成一维的字符串。也就是说,理论上geohash字符串表示的并不是一个点,而是一个矩形区域,只要矩形区域足够小,达到所需精度即可。(MongoDB的索引也是基于geohash)
如:wm3yr31d252414ux就是目前我所在的位置,降低一些精度,就会是wm3yr31d2524,再降低一些精度,就会是wm3yr。
所以这样一来,我们就可以在MySQL中用LIKE ‘wm3yr%’来限定区域范围查询目标点,并且可以对结果集做缓存。更不会因为微小的经纬度变化而无法用上数据库的Query Cache。
这种方案的优点显而易见,仅用一个字符串保存经纬度信息,并且精度由字符串从头到尾的长度决定,可以方便索引。
但这种方案的缺点是:从geohash的编码算法中可以看出,靠近每个方块边界两侧的点虽然十分接近,但所属的编码会完全不同。实际应用中,虽然可以通过去搜索环绕当前方块周围的8个方块来解决该问题,但一下子将原来只需要1次SQL查询变成了需要查询9次,这样不仅增大了查询量,也将原本简单的方案复杂化了。除此之外,这个方案也无法直接得到距离,需要程序协助进行后续的排序计算。
geohash的编码算法
成都永丰立交经纬度(30.63578,104.031601)
1)、纬度范围(-90, 90)平分成两个区间(-90, 0)、(0, 90), 如果目标纬度位于前一个区间,则编码为0,否则编码为1。
由于30.625265属于(0, 90),所以取编码为1。
然后再将(0, 90)分成 (0, 45), (45, 90)两个区间,而39.92324位于(0, 45),所以编码为0
然后再将(0, 45)分成 (0, 22.5), (22.5, 45)两个区间,而39.92324位于(22.5, 45),所以编码为1
依次类推可得永丰立交纬度编码为101010111001001000100101101010。
2)、经度也用同样的算法,对(-180, 180)依次细分,(-180,0)、(0,180) 得出编码110010011111101001100000000000
3)、合并经纬度编码,从高到低,先取一位经度,再取一位纬度;得出结果 111001001100011111101011100011000010110000010001010001000100
4)、用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,得到(30.63578,104.031601)的编码为wm3yr31d2524。
11100 10011 00011 11110 10111 00011 00001 01100 00010 00101 00010 00100 => wm3yr31d2524
算法如下:
1 |
|
方案二: MongoDB
MongoDB原生支持地理位置索引,可以直接用于位置距离计算和查询。
另外,它也是如今最流行的NoSQL数据库之一,除了能够很好地支持地理位置计算之外,还拥有诸如面向集合存储、模式自由、高性能、支持复杂查询、支持完全索引等等特性。查询结果默认将会由近到远排序,而且查询结果也包含目标点对象、距离目标点的距离等信息。由于geoNear是MongoDB原生支持的查询函数,所以性能上也做到了高度的优化,完全可以应付生产环境的压力。
1 | # 插入数据 |
方案三: Redis
自 Redis 3.2版 开始,Redis基于geohash和有序集合提供了地理位置相关功能。
Redis Geo模块的6个指令用途说明:
http://redisdoc.com/script/eval.html
1)GEOADD:将给定的位置对象(纬度、经度、名字)添加到指定的key;
2)GEOPOS:从key里面返回所有给定位置对象的位置(经度和纬度);
3)GEODIST:返回两个给定位置之间的距离;
4)GEOHASH:返回一个或多个位置对象的Geohash表示;
5)GEORADIUS:以给定的经纬度为中心,返回目标集合中与中心的距离不超过给定最大距离的所有位置对象;
6)GEORADIUSBYMEMBER:以给定的位置对象为中心,返回与其距离不超过给定最大距离的所有位置对象。
1 | # 插入数据 |
注意:
1 |
|
附录:两点距离算法
1 | /** |