资料来源:https://www.cnblogs.com/xuwenjin/p/12715339.html
https://www.cnblogs.com/toutou/p/redis_geo.html

1、使用场景

司机在空闲时,会在司机端定时上报其位置。当乘客下单后,会通过乘客的位置查询附近司机然后进行匹配

2、GEO简介

reids在版本 3.2.0之后,引入了geo功能,可用于处理地理位置。涉及到的相关命令有:GEOADDDEODISTGEORADIUS

3、代码示例

3.1 pom依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <dependency>
  6. <!-- redis -->
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-data-redis</artifactId>
  9. </dependency>

3.2 GEO工具类

  1. @Service
  2. public class RedisGeoService {
  3. @Autowired
  4. private StringRedisTemplate redisTemplate;
  5. /**
  6. * 添加经纬度信息
  7. *
  8. * redis 命令:geoadd key 116.405285 39.904989 "北京"
  9. */
  10. public Long geoAdd(String key, Point point, String member) {
  11. if (redisTemplate.hasKey(key)) {
  12. redisTemplate.opsForGeo().remove(key, member);
  13. }
  14. return redisTemplate.opsForGeo().add(key, point, member);
  15. }
  16. /**
  17. * 查找指定key的经纬度信息,可以指定多个member,批量返回
  18. *
  19. * redis命令:geopos key 北京
  20. */
  21. public List<Point> geoGet(String key, String... members) {
  22. return redisTemplate.opsForGeo().position(key, members);
  23. }
  24. /**
  25. * 返回两个地方的距离,可以指定单位,比如米m,千米km,英里mi,英尺ft
  26. *
  27. * redis命令:geodist key 北京 上海
  28. */
  29. public Distance geoDist(String key, String member1, String member2, Metric metric) {
  30. return redisTemplate.opsForGeo().distance(key, member1, member2, metric);
  31. }
  32. /**
  33. * 根据给定的经纬度,返回半径不超过指定距离的元素
  34. *
  35. * redis命令:georadius key 116.405285 39.904989 100 km WITHDIST WITHCOORD ASC
  36. * COUNT 5
  37. */
  38. public GeoResults<RedisGeoCommands.GeoLocation<String>> nearByXY(String key, Circle circle, long count) {
  39. // includeDistance 包含距离
  40. // includeCoordinates 包含经纬度
  41. // sortAscending 正序排序
  42. // limit 限定返回的记录数
  43. RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
  44. .includeDistance().includeCoordinates().sortAscending().limit(count);
  45. return redisTemplate.opsForGeo().radius(key, circle, args);
  46. }
  47. /**
  48. * 根据指定的地点查询半径在指定范围内的位置
  49. *
  50. * redis命令:georadiusbymember key 北京 100 km WITHDIST WITHCOORD ASC COUNT 5
  51. */
  52. public GeoResults<RedisGeoCommands.GeoLocation<String>> nearByPlace(String key, String member, Distance distance,
  53. long count) {
  54. // includeDistance 包含距离
  55. // includeCoordinates 包含经纬度
  56. // sortAscending 正序排序
  57. // limit 限定返回的记录数
  58. RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
  59. .includeDistance().includeCoordinates().sortAscending().limit(count);
  60. return redisTemplate.opsForGeo().radius(key, member, distance, args);
  61. }
  62. /**
  63. * 返回的是geohash值
  64. *
  65. * redis命令:geohash key 北京
  66. */
  67. public List<String> geoHash(String key, String member) {
  68. return redisTemplate.opsForGeo().hash(key, member);
  69. }
  70. }

3.3 司机实体类

建立一个实体,用来封装司机位置信息:

  1. @Getter
  2. @Setter
  3. @Builder
  4. @NoArgsConstructor
  5. @AllArgsConstructor
  6. public class DriverPosition {
  7. /** 司机id */
  8. private String driverId;
  9. /** 城市编码 */
  10. private String cityCode;
  11. /** 经度 */
  12. private double lng;
  13. /** 纬度 */
  14. private double lat;
  15. }

3.4 RedisGeoController类

建立一个controller,用来做测试:

  1. @RestController
  2. @RequestMapping("redisGeo")
  3. public class RedisGeoController {
  4. @Autowired
  5. private RedisGeoService redisGeoService;
  6. private final String GEO_KEY = "geo_key";
  7. /**
  8. * 使用redis + GEO,上报司机位置
  9. */
  10. @PostMapping("addDriverPosition")
  11. public List<Point> addDriverPosition(String cityId, String driverId, Double lng, Double lat) {
  12. String redisKey = GEO_KEY + ":" + cityId;
  13. Long addnum = redisGeoService.geoAdd(redisKey, new Point(lng, lat), driverId);
  14. List<Point> points = redisGeoService.geoGet(redisKey, driverId);
  15. System.out.println("添加位置坐标点:" + points);
  16. return points;
  17. }
  18. /**
  19. * 使用redis + GEO,查询附近司机位置
  20. */
  21. @GetMapping("getNearDrivers")
  22. public List<DriverPosition> getNearDrivers(String cityId, Double lng, Double lat) {
  23. String redisKey = GEO_KEY + ":" + cityId;
  24. // Circle circle = new Circle(lng, lat, Metrics.KILOMETERS.getMultiplier());
  25. Point point1 = new Point(lng, lat);
  26. Distance distance = new Distance(100000, Metrics.NEUTRAL);
  27. Circle circle = new Circle(point1, distance);
  28. GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisGeoService.nearByXY(redisKey, circle, 5);
  29. System.out.println("查询附近司机位置:" + results);
  30. List<DriverPosition> list = new ArrayList<>();
  31. results.forEach(item -> {
  32. RedisGeoCommands.GeoLocation<String> location = item.getContent();
  33. Point point = location.getPoint();
  34. DriverPosition position = DriverPosition.builder()
  35. .cityCode(cityId)
  36. .driverId(location.getName())
  37. .lng(point.getX())
  38. .lat(point.getY())
  39. .build();
  40. list.add(position);
  41. });
  42. return list;
  43. }
  44. }

通过高德地图取点4个位置,所对应的坐标分别是:东方雨林(114.366386, 30.408199)、怡景江南(114.365281, 30.406869)、梅南山居(114.368049, 30.412896)、武汉大学(114.365248, 30.537860)
其中前三个地址是在一起的,最后一个隔的很远

4、测试

使用postman,分别发送如下请求,添加司机的位置:

  1. http://localhost:18081/redisGeo/addDriverPosition?cityId=420000&driverId=000001&lng=114.366386&lat=30.408199
  2. http://localhost:18081/redisGeo/addDriverPosition?cityId=420000&driverId=000002&lng=114.365281&lat=30.406869
  3. http://localhost:18081/redisGeo/addDriverPosition?cityId=420000&driverId=000003&lng=114.368049&lat=30.412896
  4. http://localhost:18081/redisGeo/addDriverPosition?cityId=420000&driverId=000004&lng=114.365248&lat=30.537860

使用Redis Desktop Manager工具查看刚添加的数据:
使用Redis GEO实现查询附近司机 - 图1
可以看到,保存到redis的数据格式是ZSET,即有序集合。上面的key中包含了城市id,value表示司机id

接下来查询“东方雨林”附近的所有司机位置:http://localhost:18081/redisGeo/getNearDrivers?cityId=420000&lng=114.366386&lat=30.408199
控制台打印日志如下:

  1. GeoResults: [averageDistance: 242.78286666666668 METERS, results: GeoResult [content: RedisGeoCommands.GeoLocation(name=000001, point=Point [x=114.366386, y=30.408199]), distance: 0.0521 METERS, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=000002, point=Point [x=114.365281, y=30.406869]), distance: 182.0457 METERS, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=000003, point=Point [x=114.368049, y=30.412896]), distance: 546.2508 METERS, ]]

上面的结果,包含间隔距离的平均值,附近坐标点经纬度、间隔距离,同时结果是按间隔距离正序排序的

请求返回结果如下:

  1. [
  2. {
  3. "driverId": "000001",
  4. "cityCode": "420000",
  5. "lng": 114.36638563871384,
  6. "lat": 30.408199349640434
  7. },
  8. {
  9. "driverId": "000002",
  10. "cityCode": "420000",
  11. "lng": 114.3652805685997,
  12. "lat": 30.406868621031784
  13. },
  14. {
  15. "driverId": "000003",
  16. "cityCode": "420000",
  17. "lng": 114.36804860830307,
  18. "lat": 30.412896187948697
  19. }
  20. ]

再来试下“武汉大学”附近的司机位置,请求返回结果如下:

  1. [
  2. {
  3. "driverId": "000004",
  4. "cityCode": "420000",
  5. "lng": 114.36524838209152,
  6. "lat": 30.537860475825262
  7. }
  8. ]