redis3.2发布rc版本已经有一段时间了,估计RedisConf 2016左右,3.2版本就能release了。3.2版本中增长的最大功能就是对GEO(地理位置)的支持。提及Redis的GEO特性,最大的贡献仍是我们中国人。redis做者在对3.2引进新特性的博客中介绍了为何支持GEO。GEO hashing的api是在Ardb实现的,Ardb是github用户yinqiwen实现的基于redis协议实现的nosql系统,Ardb支持除了redis、还有LevelDB、RocksDB
、LMDB等kv引擎。其中Ardb实现了GEO hashing功能。从Ardb做者的用户名和标识的位置在深圳能够看出Ardb做者应该是咱中国人。Ardb是用c++写的。redis另外一个开发者Matt Stancliff从Ardb提取GEO库,用C语言改写,整合进redis的一个本身的分支,并被redis做者接受,合并进了3.2版本。GEO目前提供如下6个命令。html
地理位置的坐标是以WGS84为标准,WGS84,全称World Geodetic System 1984,是为GPS全球定位系统使用而创建的坐标系统。java
下面来看看具体每一个命令的用法。c++
geoadd用来增长地理位置的坐标,能够批量添加地理位置,命令格式为:git
GEOADD key longitude latitude member [longitude latitude member ...]
key标识一个地理位置的集合。longitude latitude member
标识了一个地理位置的坐标。longitude是地理位置的经度,latitude是地理位置的纬度。member是该地理位置的名称。GEOADD能够批量给集合添加一批地理位置。github
geopos能够获取地理位置的坐标,能够批量获取多个地理位置的坐标,命令格式为:redis
GEOPOS key member [member ...]
geodist用来获取两个地理位置的距离,命令格式为:算法
GEODIST key member1 member2 [m|km|ft|mi]
单位能够指定为如下四种类型:sql
georadius能够根据给定地理位置坐标获取指定范围内的地理位置集合。命令格式为:apache
GEORADIUS key longitude latitude radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count]
longitude latitude
标识了地理位置的坐标,radius表示范围距离,距离单位能够为m|km|ft|mi,还有一些可选参数:api
georadiusbymember能够根据给定地理位置获取指定范围内的地理位置集合。georadius命令传递的是坐标,georadiusbymember传递的是地理位置。georadius更为灵活,能够获取任何坐标点范围内的地理位置。可是大多数时候,只是想获取某个地理位置附近的其余地理位置,使用georadiusbymember则更为方便。georadiusbymember命令格式为(命令可选参数与georadius含义同样):
GEORADIUSBYMEMBER key member radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count]
geohash能够获取某个地理位置的geohash值。geohash是将二维的经纬度转换成字符串hash值的算法,后面会具体介绍geohash原理。能够批量获取多个地理位置的geohash值。命令格式为:
GEOHASH key member [member ...]
redis GEO实现主要包含了如下两项技术:
geohash的思想是将二维的经纬度转换成一维的字符串,geohash有如下三个特色:
这三个特性让geohash特别适合表示二维hash值。这篇文章:GeoHash核心原理解析详细的介绍了geohash的原理,想要了解geohash实现的朋友能够参考这篇文章。
知道了redis使用有序集合(zset)保存地理位置数据(想了解redis有序集合的,能够参看这篇文章《有序集合对象》),以及geohash的特性,就很容易理解redis是如何实现redis GEO命令了。细心的读者可能发现,redis没有实现地理位置的删除命令。不过因为GEO数据保存在zset中,能够用zrem来删除某个地理位置。
redis关于geohash使用了Ardb的geohash库geohash-int
,redis使用的geohash编码长度为26位。能够精确到0.59m的精度。
经过本文,拨开GEO身后的云雾,能够看出redis借助了有序集合(zset)和geohash,加上redis自己实现的命令框架,能够很容易的实现地理位置相关的命令。
package org.lanqiao.ssm.common.redis; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.alibaba.druid.pool.vendor.SybaseExceptionSorter; import com.google.common.collect.Maps; import redis.clients.jedis.GeoCoordinate; import redis.clients.jedis.GeoRadiusResponse; import redis.clients.jedis.GeoUnit; import redis.clients.jedis.Jedis; import redis.clients.jedis.params.geo.GeoRadiusParam; /** * * @Title: RedisTest.java * @Package org.lanqiao.ssm.common.redis * @Description: TODO(本地jedis支持的单节点操做redis) * @author 刘伟 15818570028@163.com * @date 2016年10月11日 上午9:29:35 * @version V1.0 */ public class RedisGeoTest { private static final Log log = LogFactory.getLog(RedisGeoTest.class); public static void main(String[] args) { testGeoadds(); testGeoradius(); } /** * * @Description:单个添加地理位置 */ public static void testGeoadd() { Jedis jedis = new Jedis("10.1.10.74", 6379); jedis.auth("123"); jedis.geoadd("citys", 116.41667, 39.91667, "beijin"); jedis.geoadd("citys", 121.48, 31.22, "Shanghai"); jedis.geoadd("citys", 117.20, 39.13, "Tianjin"); List<GeoCoordinate> geopos = jedis.geopos("citys", "beijin"); System.out.println(geopos); } /** * * @Description:批量添加地理位置 */ public static void testGeoadds() { Jedis jedis = new Jedis("10.1.10.74", 6379); jedis.auth("123"); GeoCoordinate beijin = new GeoCoordinate(116.41667, 39.91667); GeoCoordinate Shanghai = new GeoCoordinate(121.48, 31.22); GeoCoordinate Tianjin = new GeoCoordinate(117.20, 39.13); Map<String, GeoCoordinate> map = Maps.newHashMap(); map.put("cs", beijin); map.put("hh", Shanghai); map.put("sy", Tianjin); jedis.geoadd("citys", map); } /** * * @Description:georadius能够根据给定地理位置坐标获取指定范围内的地理位置集合 */ public static void testGeoradius() { Jedis jedis = new Jedis("10.1.10.74", 6379); jedis.auth("123"); GeoRadiusParam withCoord = GeoRadiusParam.geoRadiusParam().count(6).sortAscending().withDist().withCoord(); List<GeoRadiusResponse> georadius = jedis.georadius("citys", 121.48, 31.22, 5000, GeoUnit.KM, withCoord); georadius.forEach(e ->{System.out.println(e.getMemberByString());}); } }