学习目标:
1、了解GeoHash 算法
2、掌握redis对GeoHash的命令的支持
学习过程:
一、了解GeoHash 算法
地图上一般是使用经度和纬度两个维度来唯一的确定一个点,而geohash采用经纬度二维值转为一维的值。只需要对一个字段进行索引,提高性能、降低复杂度,可转成可排序,可比较的字符串,满足灵活的需求,具体的GeoHash的说明可以参考维基百科。
https://en.wikipedia.org/wiki/Geohash
GeoHash 算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。当我们想要计算「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。
目前很多数据库都对GeoHash支持了,比如mysql、mongodb、hbase等都对其实现了支持。在真正的生产环境中,需要根据需要采用不通的技术,并不一定使用redis。
二、redis的geohas的命令
在 Redis 里面,经纬度的信息是使用zset 储存的,经纬度使用 52 bit的整数进行编码,score 是 值就是该的 52 位整数值, value 就是元素的 key。zset 的 score 虽然是浮点数,但是对于 52 位的整数值,它可以无损存储。
redis的相关GetHash命令就是对zset的封装使用,在添加信息是会把经纬度转换,在比较时会使用排序等算法,同时也可以把score重新转换位经纬度信息,以获得原始的坐标。
首先我们先获得对应的经纬度信息
http://api.map.baidu.com/lbsapi/getpoint/index.html
redis支持的GeoHash相关命令
geoadd 添加
geopos 获取元素经纬度
geohash 获取base32 编码
geodist 计算距离
georadiusbymember 根据member获得距离最近的信息
georadius 根据指定的经纬度获得信息
1、geoadd 添加,可以一次一个,也可以一次多个,我这里添加了几个车站的坐标信息
127.0.0.1:6379> geoadd station 113.348694 23.176776 tianhekeyunzhan
(integer) 1
127.0.0.1:6379> geoadd station 113.330584 23.156044 guangzhoudongzhan 113.258289 23.15498 shengkehuzhan 113.278986 23.165347 guangyuankyz
(integer) 3
2、geopos 获取元素经纬度
我们获取一下刚才添加的坐标信息,可以看到精度和添加的时候有点出入。
127.0.0.1:6379> geopos station tianhekeyunzhan
1) 1) "113.34869652986526489"
2) "23.17677675698901396"
3、geohash 获取base32 编码
127.0.0.1:6379> geohash station tianhekeyunzhan
1) "ws0eg74n800"
4、geodist 计算距离,计算两个元素之间的距离
127.0.0.1:6379> geodist station tianhekeyunzhan guangzhoudongzhan km
"2.9577"
127.0.0.1:6379> geodist station tianhekeyunzhan guangzhoudongzhan m
"2957.7020"
5、georadiusbymember 根据member获得距离最近的信息
GEORADIUSBYMEMBER location-set
location radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]
这个命令参数比较多
比如我们要查询以tianhekeyunzhan为中心,半径30KM,最近的前三个,并显示距离WITHDIST,注意这个命令会包含自己的。
127.0.0.1:6379> georadiusbymember station tianhekeyunzhan 30 km WITHDIST count 3 asc
1) 1) "tianhekeyunzhan"
2) "0.0000"
2) 1) "guangzhoudongzhan"
2) "2.9577"
3) 1) "guangyuankyz"
2) "7.2407"
6、georadius 根据指定的经纬度获得信息
GEORADIUS location-set
longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]
这个命令和上一个参数几乎一样,就是经纬度由自己输入,这个在定位导航时更常见,比如我现在在某个地方,想查询离我最近的车站在哪里,就可以使用这个命令
127.0.0.1:6379> GEORADIUS station 113.301407 23.146075 30 km withdist count 5 asc
1) 1) "guangyuankyz"
2) "3.1389"
2) 1) "guangzhoudongzhan"
2) "3.1834"
3) 1) "shengkehuzhan"
2) "4.5195"
4) 1) "tianhekeyunzhan"
2) "5.9201"
三、JRedis对GeoHash的封装和使用
添加信息
@Test
public void testGeoAdd() {
Map<Serializable, Point> memberCoordinateMap = new HashMap<Serializable, Point>();
memberCoordinateMap.put("fangcunkehuzhan", new Point(113.24166, 23.085442));
memberCoordinateMap.put("jiaokoukehuzhan", new Point(113.214794, 23.119942));
long result = template.opsForGeo().geoAdd("station", memberCoordinateMap);
System.out.println(result);
}
查询
@Test
public void testGeoRadiusByMember() {
//KILOMETERS 千米
Distance distance=new Distance(30D,Metrics.KILOMETERS);
GeoRadiusCommandArgs geoArgs=GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(5).sortAscending();
GeoResults<GeoLocation<Serializable>> result= template.opsForGeo().geoRadiusByMember("station", "fangcunkehuzhan",distance,geoArgs);
Iterator<GeoResult<GeoLocation<Serializable>>> itero = result.iterator();
while(itero.hasNext()) {
GeoResult<GeoLocation<Serializable>> geoResult=itero.next();
System.out.println(geoResult.getContent().getName()+":"+geoResult.getDistance().getValue());
}
}
@Test
public void testGeoRadius() {
Distance distance=new Distance(30D,Metrics.KILOMETERS);
Circle within=new Circle(new Point(113.212197,23.120106), distance);
GeoRadiusCommandArgs geoArgs=GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(5).sortAscending();
GeoResults<GeoLocation<Serializable>> result= template.opsForGeo().geoRadius("station", within,geoArgs);
Iterator<GeoResult<GeoLocation<Serializable>>> itero = result.iterator();
while(itero.hasNext()) {
GeoResult<GeoLocation<Serializable>> geoResult=itero.next();
System.out.println(geoResult.getContent().getName()+":"+geoResult.getDistance().getValue());
}
}