这是 Java 和 Go 语言的 Redis 地理位置实现示例。直接上代码。
Java:
- 注入 StringRedisTemplate
private static final String USER_LOCATION_KEY = "user_location";
private final StringRedisTemplate stringRedisTemplate;
- 存储五个用户的地理位置
/**
* 存储用户地理坐标信息
*
* @param userId 用户 ID
* @param longitude 经度(-180 - 180)
* @param latitude 纬度(-90 - 90)
* <p></p>
* 根据坐标反查地址:http://api.map.baidu.com/lbsapi/getpoint/index.html
*
* <p></p>
* 一般情况下,小数点后保留 5 到 6 位足以精确到街道级别。例如,保留到小数点后 6 位,精度约为 0.1 米。<p><p>
*
* 模拟数据<p>
* localhost/geo/saveUserLocation/1/116.000000/40.123456
* localhost/geo/saveUserLocation/2/116.000100/40.123456
* localhost/geo/saveUserLocation/3/116.000210/40.123456
* localhost/geo/saveUserLocation/4/116.000330/40.123456
* localhost/geo/saveUserLocation/5/116.000460/40.123456
*/
@GetMapping("saveUserLocation/{userId}/{longitude}/{latitude}")
public void saveUserLocation(@PathVariable String userId, @PathVariable double longitude, @PathVariable double latitude) {
stringRedisTemplate.opsForGeo().add(USER_LOCATION_KEY, new Point(longitude, latitude), userId); // 底层用zset实现
}
可以看到,Redis 通过使用 ZSET 存储经纬度信息,经纬度数据通过 GeoHash 进行编码,再通过奇偶互插的方式组成一个 score。
- 根据用户ID,获取离自己最近的用户
/**
* 注意:引入redisson会让Distance的Metric属性被覆盖,导致方法失效
* 获取距离自己最近的人,基于Redis GEORADIUS
* <p>
* 业务场景:查找附近的东西/实时共享位置(轮询获取)/同城
*/
@GetMapping("findNearestUser/{userId}")
public ResponseEntity<?> findNearestUser(@PathVariable String userId) {
// 获取请求用户的地理位置坐标
List<Point> points = stringRedisTemplate.opsForGeo().position(USER_LOCATION_KEY, userId);
if (CollectionUtils.isEmpty(points)) {
return null;
}
Point point = points.get(0);
// 查找离该用户最近的limit位用户
GeoResults<RedisGeoCommands.GeoLocation<String>> radius = stringRedisTemplate
.opsForGeo()
.radius(USER_LOCATION_KEY,
new Circle(point, new Distance(100)),
RedisGeoCommands
.GeoRadiusCommandArgs
.newGeoRadiusArgs()
.includeCoordinates() // 如果不指定此参数,查询结果将不包含用户的坐标信息。
.includeDistance() // 如果不指定此参数,查询结果将不包含距离信息。
.sortAscending() // 进行升序排序,即按距离从近到远的顺序排列
.limit(10));
return ok(radius);
}
输入 userId 为4的人进行查询,查出的结果如下,可以看到离4最近的人按照从近到远依次排列
// 20230419105235
// http://localhost/geo/findNearestUser/4
{
"averageDistance": {
"value": 13.77858,
"metric": "METERS"
},
"content": [
{
"content": {
"name": "4",
"point": {
"x": 116.00032836198807,
"y": 40.12345603963592
}
},
"distance": {
"value": 0.0,
"metric": "METERS"
}
},
{
"content": {
"name": "3",
"point": {
"x": 116.00021034479141,
"y": 40.12345603963592
}
},
"distance": {
"value": 10.0374,
"metric": "METERS"
}
},
{
"content": {
"name": "5",
"point": {
"x": 116.00046247243881,
"y": 40.12345603963592
}
},
"distance": {
"value": 11.4061,
"metric": "METERS"
}
},
{
"content": {
"name": "2",
"point": {
"x": 116.00009769201279,
"y": 40.12345603963592
}
},
"distance": {
"value": 19.6185,
"metric": "METERS"
}
},
{
"content": {
"name": "1",
"point": {
"x": 116.00000113248825,
"y": 40.12345603963592
}
},
"distance": {
"value": 27.8309,
"metric": "METERS"
}
}
]
}
- 其他方法
/**
* 获取两个用户之间的距离
*
* @param userId1 用户 ID 1
* @param userId2 用户 ID 2
* @return 两个用户之间的距离,单位为米
*/
@GetMapping("getUserDistance/{userId1}/{userId2}")
public ResponseEntity<Double> getUserDistance(@PathVariable String userId1, @PathVariable String userId2) {
// 计算两个用户之间的距离
Distance distance = stringRedisTemplate.opsForGeo().distance(USER_LOCATION_KEY, userId1, userId2); // Metric默认米
if (distance == null) {
return status(HttpStatus.BAD_REQUEST).body(null);
}
// 返回距离(单位为米)
return ok(distance.getValue());
}
/**
* 获取用户的地理坐标信息
*
* @param userId 用户 ID
* @return 用户的地理坐标信息
*/
@GetMapping("getUserLocation/{userId}")
public Point getUserLocation(@PathVariable String userId) {
List<Point> points = stringRedisTemplate.opsForGeo().position(USER_LOCATION_KEY, userId); // 可以传多个userId
if (CollectionUtils.isEmpty(points)) {
return null;
}
Point point = points.get(0);
return new Point(round(point.getX(), 6), round(point.getY(), 6));
}
/**
* 四舍五入
*
* @param value 值
* @param places 小数点后几位
*/
private static double round(double value, int places) {
return BigDecimal.valueOf(value).setScale(places, RoundingMode.HALF_UP).doubleValue();
}
Go:
package api
import (
"context"
"github.com/redis/go-redis/v9"
"math"
)
type GeoApi struct {
redisClient *redis.Client
}
const (
UserLocationKey = "user_location"
)
// NewGeoApi 创建 GeoApi 实例
func NewGeoApi(redisClient *redis.Client) *GeoApi {
return &GeoApi{
redisClient: redisClient,
}
}
// SaveUserLocation 存自己位置
func (geoApi *GeoApi) SaveUserLocation(ctx context.Context, userId string, longitude, latitude float64) {
geoApi.redisClient.GeoAdd(ctx, UserLocationKey, &redis.GeoLocation{
Longitude: longitude,
Latitude: latitude,
Name: userId,
})
}
// GetUserLocation 拿自己位置
func (geoApi *GeoApi) GetUserLocation(ctx context.Context, userId string) (longitude, latitude float64) {
locations, _ := geoApi.redisClient.GeoPos(ctx, UserLocationKey, userId).Result()
return Round(locations[0].Longitude, 6), Round(locations[0].Latitude, 6)
}
// FindNearestUser 找我最近的人
func (geoApi *GeoApi) FindNearestUser(ctx context.Context, userId string) []redis.GeoLocation {
// 自己的坐标
userLocation, _ := geoApi.redisClient.GeoPos(ctx, UserLocationKey, userId).Result()
// 距离自己最近的user
nearestUsers, _ := geoApi.redisClient.GeoRadius(ctx,
UserLocationKey,
userLocation[0].Longitude,
userLocation[0].Latitude,
&redis.GeoRadiusQuery{
Radius: 100,
Unit: "m",
WithCoord: true,
WithDist: true,
Sort: "ASC",
Count: 3,
}).Result()
return nearestUsers
}
// GetUserDistance 两个人的距离
func (geoApi *GeoApi) GetUserDistance(ctx context.Context, userId1, userId2 string) float64 {
distance, _ := geoApi.redisClient.GeoDist(ctx, UserLocationKey, userId1, userId2, "m").Result()
return Round(distance, 6)
}
// Round 对 float64 值保留指定小数位数
func Round(value float64, places int) float64 {
multiplier := math.Pow(10, float64(places))
return math.Round(value*multiplier) / multiplier
}