Redis GEO地理位置信息的应用
- Redis GEO
- 概述
- 应用场景
- Redis GEO命令
- GEO命令演示
- Redis GEO实现附近人的功能
- 基础类
- API接口
- 接口实现
- 执行测试
Redis GEO
概述
Redis的GEO操作是一种基于地理位置信息进行操作的功能。它使用经度和纬度坐标来表示地理位置,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。
应用场景
地理围栏:通过设置地理位置的经纬度信息,可以将用户或者车辆等实体绑定在地理围栏内,当实体进出围栏时,可以触发相应的事件。
附近的人:在类似于约会、社交、旅游等场景下,可以通过Redis GEO快速查询周围的人员或景点。
配送服务:通过获取配送地址的经纬度信息,可以找到距离最近的配送员或仓库,并对订单进行分配。
地址查找:在地图应用中,可以通过Redis GEO快速查询某个地址周围的商家或服务设施。
动态信息:在滴滴、Uber等打车应用中,可以实时更新车辆的位置信息,以提供更加准确的车辆推荐和路线规划服务
Redis GEO命令
1.GEOADD添加位置信息
将一个或多个指定的地理位置(经度、纬度、名称)添加到指定的键中。
GEOADD key longitude latitude member [longitude latitude member ...]
添加一个名为cities的键,并将北京、上海、广州三个城市的经纬度和名称添加到该键中
GEOADD cities 116.4074 39.9042 Beijing 121.4737 31.2304 Shanghai 113.2644 23.1291 Guangzhou
GEOADD命令对于经纬度是有要求的:
有效的经度从-180度到180度
有效的纬度从-85.05112878度到85.05112878度
2.GEODIST查询距离
返回两个位置之间的距离。可以选择以米或千米为单位。
GEODIST key member1 member2 [unit]
可选参数unit用于指定计算距离时的单位:
m 表示单位为米
km 表示单位为千米
mi 表示单位为英里
ft 表示单位为英尺
查询北京和上海之间的距离,单位为千米
GEODIST cities Beijing Shanghai km
3.GEOHASH获取指定位置的Geohash值
返回一个或多个位置的Geohash值,该值用于对地理位置进行更快速的范围查找。
GEOHASH key member [member ...]
获取北京的Geohash值
GEOHASH cities Beijing
4.GEOPOS查询地理位置坐标
返回一个或多个位置的经度和纬度。
GEOPOS key member [member ...]
查询广州的经纬度
GEOPOS cities Guangzhou
5.GEORADIUS查找指定范围内的元素
查询给定坐标范围内的所有元素。可以通过设置排序选项来获取按距离排序的结果。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]
查找距离北京1000公里以内的城市,并按距离升序排列
GEORADIUS cities 116.4074 39.9042 1000 km ASC
6.GEORADIUSBYMEMBER查询给定成员周围的所有元素
查询给定成员周围的所有元素。可以通过设置排序选项来获取按距离排序的结果。
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]
查找距离上海最近的城市,并返回它们的名称和距离
GEORADIUSBYMEMBER cities Shanghai 100 km WITHDIST
7.ZREM删除成员
从指定键中删除一个或多个成员。
ZREM key member [member ...]
从cities键中删除广州
ZREM cities Guangzhou
GEO命令演示
# 添加位置信息
本机:0>geoadd user:location 121.48941 31.40527 'shagnhai'
"1"
# 添加多个位置信息
本机:0>geoadd user:location 121.47941 31.41527 'shanghai1' 121.47941 31.43527 'shagnhai2' 121.47941 31.40527 'shagnhai3'
"3"
# 计算距离,单位有m km ft(英尺) mi(英里)
# 计算两点间的距离,单位m
本机:0>geodist user:location shanghai shanghai1 m
"1462.1834"
# 千米:km
本机:0>geodist user:location shanghai shanghai1 km
"1.4622"
# geohash 返回一个或多个位置元素的geohash,保存Redis中是用geohash位置52点整数编码
# geohash 将二维经纬度转换成字符串,每个字符串代表一个矩形区域,该矩形区域内的经纬度点都共享一个相同的geohash字符串。
本机:0>geohash user:location shanghai shanghai1
1) "wtw6st1uuq0"
2) "wtw6sqfx5q0"
# geopos 从key里返回指定成员的位置信息
本机:0>geopos user:location shanghai shanghai1
1) 1) "121.48941010236740112"
2) "31.40526993848380499"
2) 1) "121.47941082715988159"
2) "31.41526941345740198"
# georadius:给定经纬度为中心,返回键包含的位置元素中,与中心的距离不超过给定最大距离的所有位置元素
# 范围单位:m km mi ft
# withcoord:将位置元素的经纬度一并返回
本机:0>georadius user:location 121.48941 31.40527 3000 m withcoord
1) 1) "shagnhai3"
2) 1) "121.47941082715988159"
2) "31.40526993848380499"
2) 1) "shanghai1"
2) 1) "121.47941082715988159"
2) "31.41526941345740198"
3) 1) "shanghai"
2) 1) "121.48941010236740112"
2) "31.40526993848380499"
# withdist:返回位置元素的同时,将位置元素与中心点间的距离一并返回
本机:0>georadius user:location 121.48941 31.40527 3000 m withdist
1) 1) "shagnhai3"
2) "949.2411"
2) 1) "shanghai1"
2) "1462.1719"
3) 1) "shanghai"
2) "0.0119"
# asc:根据中心位置,按照从近到远的方式返回位置元素
本机:0>georadius user:location 121.48941 31.40527 3000 m withdist asc
1) 1) "shanghai"
2) "0.0119"
2) 1) "shagnhai3"
2) "949.2411"
3) 1) "shanghai1"
2) "1462.1719"
# desc: 根据中心位置,按照从远到近的方式返回位置元素
本机:0>georadius user:location 121.48941 31.40527 3000 m withdist desc
1) 1) "shanghai1"
2) "1462.1719"
2) 1) "shagnhai3"
2) "949.2411"
3) 1) "shanghai"
2) "0.0119"
# count:获取指定数量的元素
本机:0>georadius user:location 121.48941 31.40527 3000 m withdist desc count 2
1) 1) "shanghai1"
2) "1462.1719"
2) 1) "shagnhai3"
2) "949.2411"
# georadiusbymember:和georadius命令类似,都可以找出指定位置范围内的元素,但是georadiusbymember的中心点是由给定位置元素决定的,而不像georadius使用经纬度决定中心点
本机:0>georadiusbymember user:location shanghai 3 km
1) "shagnhai3"
2) "shanghai1"
3) "shanghai"
Redis GEO实现附近人的功能
基础类
创建NearMeUserVO
视图对象,封装响应视图的基本数据信息
@Data
public class NearMeUserVO {
public Integer id;
/**
* 距离
*/
public String distance;
}
创建User
对象模拟操作用户
@Data
public class User {
private int id;
}
API接口
创建添加、更新用户位置信息
与查询附近用户信息
的2个API接口
@RestController
public class NearMeUserController {
@Autowired
private INearMeUserService nearMeUserService;
/**
* 添加、更新坐标
*
* @param lon
* @param lat
* @return
*/
@PostMapping("/updateUserLocation")
public String updateUserLocation(@RequestParam Float lon, @RequestParam Float lat) {
nearMeUserService.updateUserLocation(lon, lat);
return "OK";
}
/**
* 获取附近的人
* 查询距离指定经纬度一定范围内的用户,并返回结果列表
*
* @param radius
* @param lon
* @param lat
* @return
*/
@GetMapping("/getNearMe")
public Object nearMe(Integer radius, Float lon, Float lat) {
List<NearMeUserVO> nearMe = nearMeUserService.findNearMe(radius, lon, lat);
return nearMe;
}
}
接口实现
public interface INearMeUserService {
/**
* 更新坐标
*
* @param lon 经度
* @param lat 纬度
*/
void updateUserLocation(Float lon, Float lat);
/**
* 获取附近的人
*
* @param radius 半径,默认 1000m
* @param lon 经度
* @param lat 纬度
* @return
*/
List<NearMeUserVO> findNearMe(Integer radius, Float lon, Float lat);
}
@Service
public class NearMeUserServiceImpl implements INearMeUserService {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
/**
* 用户定位信息cKEY
*/
private static final String USER_LOCATION_KEY = "user:location";
public void updateUserLocation(Float lon, Float lat) {
if (lon == null || lat == null) {
throw new RuntimeException("获取经度、维度失败");
}
// 获取登录用户信息
User user = this.getLoginUser();
// 定义key
String key = "user:location";
// 将用户地理位置信息存入 Redis
RedisGeoCommands.GeoLocation geoLocation = new RedisGeoCommands.GeoLocation(user.getId(), new Point(lon, lat));
redisTemplate.opsForGeo().add(key, geoLocation);
}
/**
* 获取附近的人
*
* @param radius 半径,默认 1000m
* @param lon 经度
* @param lat 纬度
* @return
*/
public List<NearMeUserVO> findNearMe(Integer radius, Float lon, Float lat) {
// 获取登录用户信息
User user = this.getLoginUser();
int userId = user.getId();
// 处理半径,默认 1000m
if (radius == null) {
radius = 1000;
}
// 获取用户经纬度
Point point = null;
if (lon == null || lat == null) {
// 如果经纬度没传,那么从 Redis 中获取
List<Point> points = redisTemplate.opsForGeo().position(USER_LOCATION_KEY, userId);
if (points == null || points.isEmpty()) {
throw new RuntimeException("获取经纬度失败");
}
point = points.get(0);
} else {
point = new Point(lon, lat);
}
// 初始化距离对象,单位 m
Distance distance = new Distance(radius, RedisGeoCommands.DistanceUnit.METERS);
// 初始化 Geo 命令参数对象
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();
// 附近的人限制3,包含距离,按由近到远排序
args.limit(3).includeDistance().sortAscending();
// 以用户经纬度为圆心,范围 1000m
Circle circle = new Circle(point, distance);
// 获取附近的人 GeoLocation 信息
GeoResults<RedisGeoCommands.GeoLocation<Object>> geoResult = redisTemplate.opsForGeo().radius(USER_LOCATION_KEY, circle, args);
// 构建有序 Map
Map<Integer, NearMeUserVO> nearMeUserVOMap = Maps.newLinkedHashMap();
// 完善用户信息
geoResult.forEach(result -> {
RedisGeoCommands.GeoLocation<Object> geoLocation = result.getContent();
// 初始化Vo对象
NearMeUserVO nearMeUserVO = new NearMeUserVO();
nearMeUserVO.setId((Integer) geoLocation.getName());
// 获取距离
Double dist = result.getDistance().getValue();
// 四舍五入精确到小数点后 1 位,方便客户端显示
String distanceStr = NumberUtil.round(dist, 1).toString() + "m";
nearMeUserVO.setDistance(distanceStr);
nearMeUserVOMap.put((Integer) geoLocation.getName(), nearMeUserVO);
});
// 附近的人,可进一步完善用户信息
// Integer[] userIds = nearMeUserVOMap.keySet().toArray(new Integer[0]);
ArrayList<NearMeUserVO> nearMeUserVO = Lists.newArrayList(nearMeUserVOMap.values());
return nearMeUserVO;
}
/**
* 模拟获取真实登录用户信息
*
* @return
*/
public User getLoginUser() {
User user = new User();
user.setId(1);
return user;
}
}
执行测试
访问执行添加用户地理位置数据
接口,得到如下所示数据:
访问执行获取附近人
接口,得到如下所示数据:
"data": [
{
"id": 1,
"distance": "0.0m"
},
{
"id": 3,
"distance": "949.3m"
},
{
"id": 2,
"distance": "1462.2m"
}
]