相信大家都用过打车软件、或者使用地图软件搜索过附近的公交站台、商场、地铁口等等,这种都离不开基于位置服务(Location-Based Service,LBS)的应用。此类应用都是基于经纬度来查询附近的目标,Redis GEO 就适用于此类场景。
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。Redis GEO 支持如下操作方法:
geoadd:添加地理位置的坐标。
geopos:获取地理位置的坐标。
geodist:计算两个位置之间的距离。
georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
geohash:返回一个或多个位置对象的 geohash 值。
Redis GEO 并不是一种新的数据结构,而是基于 Sorted Set 实现的。Sorted Set 结构保存的数据形式是 key-score,即一个元素对应一个分值,默认是根据分值排序的,且可以进行范围查询。但是经纬度是一个数据对,比如(104.072206,30.663486),那么 Redis 是如何将经纬度转换成 score 值的呢?转换成 score 值之后,是如何保证分值相邻的元素距离也相近的呢?这一切就依赖于 GeoHash 编码。
在 Spring Data Redis 中,可以使用 opsForGeo() 方法获取到 GeoOperations 操作接口。代码如下:
GeoOperations<String,String> ops = redisTemplate.opsForGeo();
使用 add() 方法可以向 Redis 中添加一个或多个地理位置信息,方法定义如下:
Long add(K key, Iterable<RedisGeoCommands.GeoLocation<M>> locations) 将 RedisGeoCommands.GeoLocations 添加到键
Long add(K key, Map<M,org.springframework.data.geo.Point> memberCoordinateMap) 将 member / Point map 键值对添加到键
Long add(K key, org.springframework.data.geo.Point point, M member) 将具有给定成员名称的 Point 添加到键
Long add(K key, RedisGeoCommands.GeoLocation<M> location) 将 RedisGeoCommands.GeoLocation 添加到键
示例:
GeoOperations<String,String> ops = redisTemplate.opsForGeo();
// 成都天府广场坐标
Point point = new Point(104.072206,30.663486);
ops.add("geo-key", point, "chengdu");
// 北京天安门广场坐标
point = new Point(116.403039,39.91513);
ops.add("geo-key", point, "beijing");
// 获取地理坐标
List<Point> pointList = ops.position("geo-key", "chengdu", "beijing");
for(Point item : pointList) {
System.out.println("x=" + item.getX() + ", y=" + item.getY());
}运行示例,输出结果如下:
x=104.0722069144249, y=30.663486325924417 x=116.40304058790207, y=39.915129842271014
使用 distance() 方法可以计算两个地理位置之间的距离,方法定义如下:
org.springframework.data.geo.Distance distance(K key, M member1, M member2) 计算 member1 和 member2 之间的距离
org.springframework.data.geo.Distance distance(K key, M member1, M member2, org.springframework.data.geo.Metric metric) 计算 member1 和 member2 之间的距离,使用指定的度量单位返回距离
示例:
GeoOperations<String,String> ops = redisTemplate.opsForGeo();
// 成都天府广场坐标
Point point = new Point(104.072206,30.663486);
ops.add("geo-key", point, "chengdu");
// 北京天安门广场坐标
point = new Point(116.403039,39.91513);
ops.add("geo-key", point, "beijing");
// 计算成都到北京的距离
Distance distance = ops.distance("geo-key", "chengdu", "beijing");
System.out.println("相距:" + distance.getValue() + distance.getUnit());
// Metrics.KILOMETERS 公里
// Metrics.MILES 英里
// Metrics.NEUTRAL 米
distance = ops.distance("geo-key", "chengdu",
"beijing", Metrics.MILES);
System.out.println("相距:" + distance.getValue() + distance.getUnit());
distance = ops.distance("geo-key", "chengdu",
"beijing", Metrics.KILOMETERS);
System.out.println("相距:" + distance.getValue() + distance.getUnit());
distance = ops.distance("geo-key", "chengdu",
"beijing", Metrics.NEUTRAL);
System.out.println("相距:" + distance.getValue() + distance.getUnit());运行示例,输出结果如下:
相距:1517797.1972m 相距:943.1178mi 相距:1517.7972km 相距:1517797.1972m
使用 hash() 方法获取指定地理位置的 GeoHash 值,方法定义如下:
List<String> hash(K key, M... members) 获取一个或多个 member 的 Geohash 表示形式。
示例:
GeoOperations<String,String> ops = redisTemplate.opsForGeo();
// 成都天府广场坐标
Point point = new Point(104.072206,30.663486);
ops.add("geo-key", point, "chengdu");
// 北京天安门广场坐标
point = new Point(116.403039,39.91513);
ops.add("geo-key", point, "beijing");
// 获取地理位置的GeoHash
List<String> geoHashs = ops.hash("geo-key", "chengdu", "beijing");
for(String geoHash : geoHashs) {
System.out.println(geoHash);
}运行示例,输出结果如下:
wm6n2np5f00 wx4g0f647r0
使用 position() 方法获取指定成员的地理位置信息,方法定义如下:
List<org.springframework.data.geo.Point> position(K key, M... members) 获取一个或多个成员的位置的 Point 表示形式。
示例:
GeoOperations<String,String> ops = redisTemplate.opsForGeo();
// 成都天府广场坐标
Point point = new Point(104.072206,30.663486);
ops.add("geo-key", point, "chengdu");
// 北京天安门广场坐标
point = new Point(116.403039,39.91513);
ops.add("geo-key", point, "beijing");
// 获取成都的地理位置坐标
List<Point> pointList = ops.position("geo-key", "chengdu");
if(null != pointList && pointList.size() > 0) {
Point p = pointList.get(0);
System.out.println("x=" + p.getX() + ", y=" + p.getY());
}运行示例,输出结果如下:
x=104.0722069144249, y=30.663486325924417
使用 radius() 方法可以获取目标地理位置周围指定半径中的地理位置信息,方法定义如下:
org.springframework.data.geo.GeoResults<RedisGeoCommands.GeoLocation<M>> radius(K key, org.springframework.data.geo.Circle within) 获取给定边界内的成员的地理位置信息
org.springframework.data.geo.GeoResults<RedisGeoCommands.GeoLocation<M>> radius(K key, org.springframework.data.geo.Circle within, RedisGeoCommands.GeoRadiusCommandArgs args) 使用RedisGeoCommands.GeoRadiusCommandArgs 获取给定边界内的成员的地理位置信息。
org.springframework.data.geo.GeoResults<RedisGeoCommands.GeoLocation<M>> radius(K key, M member, org.springframework.data.geo.Distance distance) 使用指定的 Metric(度量单位)和成员坐标,返回给定半径定义的圆内的其他成员地理位置信息。
org.springframework.data.geo.GeoResults<RedisGeoCommands.GeoLocation<M>> radius(K key, M member, org.springframework.data.geo.Distance distance, RedisGeoCommands.GeoRadiusCommandArgs args) 获取由成员坐标和 Metric 与 RedisGeoCommands.GeoRadiusCommandArgs 给定半径定义的圆内的成员地理位置信息。
org.springframework.data.geo.GeoResults<RedisGeoCommands.GeoLocation<M>> radius(K key, M member, double radius) 获取由成员坐标和给定半径定义的圆内的成员地理位置信息。
示例:
String[] names = {
"chengdu",
"chengdudong_railway_station",
"chengdubei_railway_station",
"beijing",
"wuhan"
};
double[][] points = {
{104.07332,30.664768},
{104.145184,30.636433},
{104.145184,30.636433},
{116.400739,39.920885},
{114.360454,30.588768}
};
GeoOperations<String,String> ops = redisTemplate.opsForGeo();
// 初始化坐标数据
int i = 0;
for(double[] point : points) {
ops.add("geo-key", new Point(point[0], point[1]), names[i++]);
}
// 获取成都附近 100 公里范围内的地理位置
GeoResults<RedisGeoCommands.GeoLocation<String>> geos =
ops.radius("geo-key", "chengdu", 100*1000);
if(null != geos) {
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> geoResults = geos.getContent();
for(GeoResult<RedisGeoCommands.GeoLocation<String>> geo : geoResults) {
RedisGeoCommands.GeoLocation<String> geoLocation = geo.getContent();
System.out.println(geoLocation.getName());
}
}运行示例,输出结果如下:
chengdubei_railway_station chengdudong_railway_station chengdu
Long remove(K key, M... members) 移除指定的成员
示例:
GeoOperations<String,String> ops = redisTemplate.opsForGeo();
// 成都天府广场坐标
Point point = new Point(104.072206,30.663486);
ops.add("geo-key", point, "chengdu");
// 删除成都地理位置
Long size = ops.remove("geo-key", "chengdu");
System.out.println("size=" + size);
// 验证是否已经被删除
List<Point> pointList = ops.position("geo-key", "chengdu");
for(Point p : pointList) {
System.out.println(p);
}运行示例,输出结果如下:
size=1 null