Redis GEO 的说明

Redis 官方对 Geo 的描述

Redis 3.2 contains significant changes to the API and implementation of Redis. A new set of commands for Geo indexing was added (GEOADD, GEORADIUS and related commands).

关于 Geo,需要知道

  • Redis 的 Geo 是在 3.2 版本才有的
  • 使用 geohash 保存地理位置的坐标
  • 使用有序集合(zset)保存地理位置的集合

6个操作命令

  • GEOADD:增加某个地理位置的坐标
  • GEOPOS:获取某个地理位置的坐标
  • GEODIST:获取两个地理位置的距离
  • GEORADIUS:根据给定地理位置坐标获取指定范围内的地理位置集合
  • GEORADIUSBYMEMBER:根据给定地理位置获取指定范围内的地理位置集合
  • GEOHASH:获取某个地理位置的 geohash 值

命令的使用方法

SpringBoot 使用 Redis Geo(实例)

说明:以城市信息为目标,使用 StringRedisTemplate 操作 Redis 提供的关于 Geo 的6个命令。

  • JDK 版本
1
2
3
4
<properties>
<java.version>1.8</java.version>
</properties>

  • SpringBoot 版本
1
2
3
4
5
6
7
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/>
</parent>

  • redis starter
1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  • vo 对象定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* <h1>城市信息</h1>
* Created by Qinyi.
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CityInfo {

/** 城市 */
private String city;

/** 经度 */
private Double longitude;

/** 纬度 */
private Double latitude;
}

  • 服务接口定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import com.imooc.ad.vo.CityInfo;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;

import java.util.Collection;
import java.util.List;

/**
* <h1>Geo 服务接口定义</h1>
* Created by Qinyi.
*/
public interface IGeoService {

/**
* <h2>把城市信息保存到 Redis 中</h2>
* @param cityInfos {@link CityInfo}
* @return 成功保存的个数
* */
Long saveCityInfoToRedis(Collection<CityInfo> cityInfos);

/**
* <h2>获取给定城市的坐标</h2>
* @param cities 给定城市 key
* @return {@link Point}s
* */
List<Point> getCityPos(String[] cities);

/**
* <h2>获取两个城市之间的距离</h2>
* @param city1 第一个城市
* @param city2 第二个城市
* @param metric {@link Metric} 单位信息, 可以是 null
* @return {@link Distance}
* */
Distance getTwoCityDistance(String city1, String city2, Metric metric);

/**
* <h2>根据给定地理位置坐标获取指定范围内的地理位置集合</h2>
* @param within {@link Circle} 中心点和距离
* @param args {@link RedisGeoCommands.GeoRadiusCommandArgs} 限制返回的个数和排序方式, 可以是 null
* @return {@link RedisGeoCommands.GeoLocation}
* */
GeoResults<RedisGeoCommands.GeoLocation<String>> getPointRadius(
Circle within, RedisGeoCommands.GeoRadiusCommandArgs args);

/**
* <h2>根据给定地理位置获取指定范围内的地理位置集合</h2>
* */
GeoResults<RedisGeoCommands.GeoLocation<String>> getMemberRadius(
String member, Distance distance, RedisGeoCommands.GeoRadiusCommandArgs args);

/**
* <h2>获取某个地理位置的 geohash 值</h2>
* @param cities 给定城市 key
* @return city geohashs
* */
List<String> getCityGeoHash(String[] cities);
}

  • 服务接口实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import com.alibaba.fastjson.JSON;
import com.imooc.ad.service.IGeoService;
import com.imooc.ad.vo.CityInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* <h1>Geo 服务接口实现</h1>
* Created by Qinyi.
*/
@Slf4j
@Service
public class GeoServiceImpl implements IGeoService {

private final String GEO_KEY = "ah-cities";

/** redis 客户端 */
private final StringRedisTemplate redisTemplate;

@Autowired
public GeoServiceImpl(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

@Override
public Long saveCityInfoToRedis(Collection<CityInfo> cityInfos) {

log.info("start to save city info: {}.", JSON.toJSONString(cityInfos));

GeoOperations<String, String> ops = redisTemplate.opsForGeo();

Set<RedisGeoCommands.GeoLocation<String>> locations = new HashSet<>();
cityInfos.forEach(ci -> locations.add(new RedisGeoCommands.GeoLocation<String>(
ci.getCity(), new Point(ci.getLongitude(), ci.getLatitude())
)));

log.info("done to save city info.");

return ops.add(GEO_KEY, locations);
}

@Override
public List<Point> getCityPos(String[] cities) {

GeoOperations<String, String> ops = redisTemplate.opsForGeo();

return ops.position(GEO_KEY, cities);
}

@Override
public Distance getTwoCityDistance(String city1, String city2, Metric metric) {

GeoOperations<String, String> ops = redisTemplate.opsForGeo();

return metric == null ?
ops.distance(GEO_KEY, city1, city2) : ops.distance(GEO_KEY, city1, city2, metric);
}

@Override
public GeoResults<RedisGeoCommands.GeoLocation<String>> getPointRadius(
Circle within, RedisGeoCommands.GeoRadiusCommandArgs args
) {

GeoOperations<String, String> ops = redisTemplate.opsForGeo();

return args == null ?
ops.radius(GEO_KEY, within) : ops.radius(GEO_KEY, within, args);
}

@Override
public GeoResults<RedisGeoCommands.GeoLocation<String>> getMemberRadius(
String member, Distance distance, RedisGeoCommands.GeoRadiusCommandArgs args
) {

GeoOperations<String, String> ops = redisTemplate.opsForGeo();

return args == null ?
ops.radius(GEO_KEY, member, distance) : ops.radius(GEO_KEY, member, distance, args);
}

@Override
public List<String> getCityGeoHash(String[] cities) {

GeoOperations<String, String> ops = redisTemplate.opsForGeo();

return ops.hash(GEO_KEY, cities);
}
}

  • 测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import com.alibaba.fastjson.JSON;
import com.imooc.ad.Application;
import com.imooc.ad.vo.CityInfo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* <h1>GeoService 测试用例</h1>
* Created by Qinyi.
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class GeoServiceTest {

/** fake some cityInfos */
private List<CityInfo> cityInfos;

@Autowired
private IGeoService geoService;

@Before
public void init() {

cityInfos = new ArrayList<>();

cityInfos.add(new CityInfo("hefei", 117.17, 31.52));
cityInfos.add(new CityInfo("anqing", 117.02, 30.31));
cityInfos.add(new CityInfo("huaibei", 116.47, 33.57));
cityInfos.add(new CityInfo("suzhou", 116.58, 33.38));
cityInfos.add(new CityInfo("fuyang", 115.48, 32.54));
cityInfos.add(new CityInfo("bengbu", 117.21, 32.56));
cityInfos.add(new CityInfo("huangshan", 118.18, 29.43));
}

/**
* <h2>测试 saveCityInfoToRedis 方法</h2>
* */
@Test
public void testSaveCityInfoToRedis() {

System.out.println(geoService.saveCityInfoToRedis(cityInfos));
}

/**
* <h2>测试 getCityPos 方法</h2>
* 如果传递的 city 在 Redis 中没有记录, 会返回什么呢 ? 例如, 这里传递的 xxx
* */
@Test
public void testGetCityPos() {

System.out.println(JSON.toJSONString(geoService.getCityPos(
Arrays.asList("anqing", "suzhou", "xxx").toArray(new String[3])
)));
}

/**
* <h2>测试 getTwoCityDistance 方法</h2>
* */
@Test
public void testGetTwoCityDistance() {

System.out.println(geoService.getTwoCityDistance("anqing", "suzhou", null).getValue());
System.out.println(geoService.getTwoCityDistance("anqing", "suzhou", Metrics.KILOMETERS).getValue());
}

/**
* <h2>测试 getPointRadius 方法</h2>
* */
@Test
public void testGetPointRadius() {

Point center = new Point(cityInfos.get(0).getLongitude(), cityInfos.get(0).getLatitude());
Distance radius = new Distance(200, Metrics.KILOMETERS);
Circle within = new Circle(center, radius);

System.out.println(JSON.toJSONString(geoService.getPointRadius(within, null)));

// order by 距离 limit 2, 同时返回距离中心点的距离
RedisGeoCommands.GeoRadiusCommandArgs args =
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(2).sortAscending();
System.out.println(JSON.toJSONString(geoService.getPointRadius(within, args)));
}

/**
* <h2>测试 getMemberRadius 方法</h2>
* */
@Test
public void testGetMemberRadius() {

Distance radius = new Distance(200, Metrics.KILOMETERS);

System.out.println(JSON.toJSONString(geoService.getMemberRadius("suzhou", radius, null)));

// order by 距离 limit 2, 同时返回距离中心点的距离
RedisGeoCommands.GeoRadiusCommandArgs args =
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(2).sortAscending();
System.out.println(JSON.toJSONString(geoService.getMemberRadius("suzhou", radius, args)));
}

/**
* <h2>测试 getCityGeoHash 方法</h2>
* */
@Test
public void testGetCityGeoHash() {

System.out.println(JSON.toJSONString(geoService.getCityGeoHash(
Arrays.asList("anqing", "suzhou", "xxx").toArray(new String[3])
)));
}
}

作者:张勤一
链接:http://www.imooc.com/article/280622?block_id=tuijian_wz
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作