Redis在3.2以后,增加了一个模块GEO,可以用于存取经纬度信息,然后通过命令可以求出两个地点之间的距离。
根据这一个特性,便可以实现一个在应用中经常见到的模块:附近的人。
大概的实现思路:
- 获取到用户信息,通过GEOADD命令添加redis中,redis会将其转换会一个52位的geohash码
- redis会将其score,以zset的存储
- 用GEORADIUS 命令,以定的经纬度为中心,求出半径范围内的所有元素
命令 | 作用 |
---|---|
GEOHASH | 返回一个或多个位置元素的 Geohash 表示 |
GEOPOS | 从key里返回所有给定位置元素的位置(经度和纬度) |
GEODIST | 返回两个给定位置之间的距离 |
GEORADIUS | 以给定的经纬度为中心, 找出某一半径内的元素 |
GEOADD | 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中 |
GEORADIUSBYMEMBER | 找出位于指定范围内的元素,中心点是由给定的位置元素决定 |
以下是部分关键代码:
entity:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
private Integer id;
/**
* 名称
*/
private String name;
/**
* 经度
*/
private Double longitude;
/**
* 纬度
*/
private Double latitude;
/**
* 经纬度所计算的geohash码
*/
private String geoCode;
private double Distance;
public User(Integer id, String name, double longitude, double latitude) {
this.id = id;
this.name = name;
this.longitude = longitude;
this.latitude = latitude;
}
}
controller:
@RestController
public class TestController {
@Autowired
private PeopleNearbyService nearbyService;
@RequestMapping("hello")
public String hello() {
return "hello";
}
/**
* 保存用户信息
*
* @param user
* @return
*/
@RequestMapping(value = "saveInfo", method = RequestMethod.POST)
public boolean saveInfo(@RequestBody User user) {
return nearbyService.save(user);
}
/**
* 查询指定距离(km)范围内的用户
*
* @param distance 距离
* @param userLng 经度
* @param userLat 纬度
* @return
*/
@RequestMapping("/searchNearby")
public List<User> searchNearby(@RequestParam("distance") double distance,
@RequestParam("userLng") double userLng,
@RequestParam("userLat") double userLat) {
return nearbyService.nearBySearch(distance, userLng, userLat);
}
/**
* 查询指定距离(km)范围内的用户
*
* @param distance
* @param name
* @return
*/
@RequestMapping("/searchNearbyName")
public List<User> searchNearbyName(@RequestParam("distance") double distance,
@RequestParam("name") String name) {
return nearbyService.searchNearbyName(distance, name);
}
}
service:
@Service
public class PeopleNearbyService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
//GEO相关命令用到的KEY
private final static String KEY = "user_point";
public boolean save(User user) {
Long flag = redisTemplate.opsForGeo().add(KEY, new RedisGeoCommands.GeoLocation<>(
user.getName(),
new Point(user.getLongitude(), user.getLatitude()))
);
boolean result = flag != null && flag > 0;
return result;
}
/**
* 验证经纬度距离地址:经纬度距离计算 (hhlink.com)
* 根据当前位置获取附近指定范围内的用户
*
* @param distance 指定范围 单位km ,可根据{@link Metrics} 进行设置
* @param userLng 用户经度
* @param userLat 用户纬度
* @return
*/
public List<User> nearBySearch(double distance, double userLng, double userLat) {
List<User> users = new ArrayList<>();
Distance distanceArg = new Distance(distance, Metrics.KILOMETERS);
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance()
.includeCoordinates().sortAscending();
// 1.GEORADIUS获取附近范围内的信息
GeoResults<RedisGeoCommands.GeoLocation<Object>> reslut =
redisTemplate.opsForGeo().radius(KEY, new Circle(new Point(userLng, userLat), distanceArg), args);
//2.收集信息,存入list
List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = reslut.getContent();
//3.返回计算后的信息
content.forEach(a -> users.add(
new User().setName(a.getContent().getName().toString())
.setDistance(a.getDistance().getValue() * 1000) //因为返回的结果单位与查询参数的一致(km),因此将其扩大为m
.setLatitude(a.getContent().getPoint().getX())
.setLongitude(a.getContent().getPoint().getY())));
return users;
}
/**
* 查询指定用户,范围内的用户
*
* @param distance
* @param name
* @return
*/
public List<User> searchNearbyName(@RequestParam("distance") double distance, @RequestParam("name") String name) {
List<User> users = new ArrayList<>();
List<Point> position = redisTemplate.opsForGeo().position(KEY, name);
Distance distanceArg = new Distance(distance, Metrics.KILOMETERS);
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance()
.includeCoordinates().sortAscending();
// 1.GEORADIUS获取附近范围内的信息
GeoResults<RedisGeoCommands.GeoLocation<Object>> reslut =
redisTemplate.opsForGeo().radius(KEY, new Circle(position.get(0), distanceArg), args);
//2.收集信息,存入list
List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = reslut.getContent();
//3.返回计算后的信息
content.forEach(a -> {
if (!name.equals(a.getContent().getName().toString())) {
users.add(new User().setName(a.getContent().getName().toString())
.setDistance(a.getDistance().getValue() * 1000) //因为返回的结果单位与查询参数的一致(km),因此将其扩大为m
.setLatitude(a.getContent().getPoint().getX())
.setLongitude(a.getContent().getPoint().getY()));
}
});
return users;
}
}
参考地址:
https://www.redis.net.cn/order/3685.html
经纬度距离计算 (hhlink.com)
demo地址:
https://gitee.com/LiuJunjie0921/springboot_redis_nearby