文章目录
- 1. 前言
- 2. 常用api介绍
- 3. 需求假设(获取离我最近的停车场)
- 4. 代码示例
1. 前言
接着上一篇Redis那些事儿(二) ,这一篇主要介绍Redis基于Geo数据结构实现的地理服务,它提供了一种方便的方式来存储和处理与地理位置相关的数据。Geo数据结构是Redis的一种特殊数据类型,用于存储地理位置信息,每个地理位置被表示为经度和纬度的坐标,可以将这些坐标与一个或多个成员关联起来。Redis的地理服务提供了一套简单而强大的功能,可以方便地存储和处理与地理位置相关的数据,它适用于许多应用场景,如地理定位、附近的人、附近的店铺搜索、附近的停车场、附近的地铁站…等等,大大提升了定位排序的效率。
2. 常用api介绍
Redis地理服务API方法包括:GEOADD(向Geo数据结构中添加一个或多个地理位置信息);GEODIST(计算两个地理位置之间的距离);GEORADIUS(获取给定地理位置附近一定范围内的成员);GEOPOS(获取给定成员的经纬度坐标);GEOHASH(获取给定成员的Geohash值)…以上都是Geo地理服务内置的常用方法,接下来还是基于开发中的StringRedisTemplate对象作为切入点,更直观地说明实际应用中对于Geo地理服务地应用。
StringRedisTemplate中定义了RedisGeoCommands的接口,RedisGeoCommands中封装了一系列的内置方法及子类,所以Redis中基于opsForGeo()的操作都离不开RedisGeoCommands,如下为部分截图:
Geo数据结构中存入坐标数据,redisTemplate.opsForGeo().add(key, locations)
List<Park> parks = getParks(); //TODO 获取停车场列表信息//初始化Redis区域对象集合List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>();for (Park park : parks) {//实例化ponit对象,传参[经度、纬度]Point point = new Point(park.getLng(), park.getLat());//构造location对象,传参[name值(一般取ID)、point对象]RedisGeoCommands.GeoLocation<String> location = new RedisGeoCommands.GeoLocation<>(park.getParkId() + "", point);locations.add(location);}String key = "GEO_PARK_KEY";//存入坐标数据redisTemplate.opsForGeo().add(key, locations);
Geo数据结构中删除坐标数据,redisTemplate.opsForGeo().remove(key, …members)
String key = "GEO_PARK_KEY";//删除单个坐标数据(parkId为单个停车场ID,类型为String)redisTemplate.opsForGeo().remove(key, parkId);//删除多个坐标数据(第一个参数为key,后面可以传入多个parkId)redisTemplate.opsForGeo().remove(key, parkId1, parkId2, parkId3);
Geo数据结构中检索坐标数据由近到远,redisTemplate.opsForGeo().radius(key, within, args)
String key = "GEO_PARK_KEY";//检索20公里内的Distance distance = new Distance(20, Metrics.KILOMETERS);//以目标坐标为圆心,distance为半径的圆圈范围,其中lng代表中心坐标的经度、lat代表中心坐标的纬度Circle within = new Circle(new Point(lng, lat), distance);//条件参数,按照距离查询,默认就是升序RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance();//执行查询GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().radius(key, within, args);
Geo数据结构中检索坐标数据由近到远,redisTemplate.opsForGeo().search(key, reference, distance, args)
String key = "GEO_PARK_KEY";//检索20公里内的Distance distance = new Distance(20, Metrics.KILOMETERS);//条件参数,按照距离查询,默认就是升序RedisGeoCommands.GeoSearchCommandArgs args = RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance();//中心点位确认,其中lng代表中心坐标的经度、lat代表中心坐标的纬度GeoReference<String> reference = GeoReference.fromCoordinate(lng, lat);//执行查询GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().search(key, reference, distance, args);
以上列了四个最常用的方式,类似于我们最常规的CURD,其中最后两个radius和search是查询方法,二者最终查询的结果是一致的,只是手段方式不同而已!
3. 需求假设(获取离我最近的停车场)
这个时候有人就说了:我使用GeodeticCalculator工具类在代码中计算距离也很方便的啊。我想了想,确实很方便,只需要引入geodesy的依赖,就可以直接使用GeodeticCalculator的calculateGeodeticCurve方法就可以计算了,还不需要麻烦的用redis搞那么长的代码了。但是,问题来了,就以停车场为例,假如只有十几个停车场,遍历一下然后按升序排个序,很快就计算出了离我最近的停车场列表了。如果我有上千个或者上万个停车场,总不能遍历上万次然后再排序吧,那这速度就一言难尽了…如果这个时候使用redis的geo数据结构来读取,那就完美解决这个问题了。Redis提供的GeoHash算法功能对于这方面的需求就太好用了,那么,附近的停车场、附近的人、附近的商家就都是一个思路了!
4. 代码示例
/*** 获取距离最近的停车场列表,由近到远* @param lng 当前位置经度* @param lat 当前位置纬度* @param page 页数(第n页)* @param size 每页数量(10、20...)* @param value 公里范围内(搜索范围半径)* @return*/public List<Park> getLatestParks(Double lng, Double lat, Integer page, Integer size, Double value){//计算分页起始参数Integer start = (page - 1) * size;Integer end = page * size;//查询redis,按照距离排序String key = "GEO_PARK_KEY";//检索value公里内的Distance distance = new Distance(value, Metrics.KILOMETERS);//以目标坐标为圆心,distance为半径的圆圈范围,其中lng代表中心坐标的经度、lat代表中心坐标的纬度Circle within = new Circle(new Point(lng, lat), distance);//条件参数,按照距离查询,默认就是升序,截止到endRedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(end);//执行查询GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().radius(key, within, args);if (results == null) {return new ArrayList<>();}//获取最终检索的内容List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = results.getContent();//截取从起始到结束,如果总数小于起始数就证明已经页数超了,返回空集合if (content.size() <= start) {return new ArrayList<>();}//初始化parkId集合List<Long> parkIds = new ArrayList<>();//初始化距离mapMap<String, Distance> distanceMap = new HashMap<>();//分页跳过之前的数据,并遍历赋值content.stream().skip(start).forEach(i->{String parkIdStr = i.getContent().getName();parkIds.add(Long.valueOf(parkIdStr));Distance dis = i.getDistance();distanceMap.put(parkIdStr, dis);});//固定排序String join = StringUtils.join(parkIds,",");//根据parkId集合获取park集合List<Park> newParks = parkService.list(new QueryWrapper<Park>().in("park_id", parkIds).last("ORDER BY FIELD(park_id," + join + ")")).stream().map(i -> {i.setDistance(distanceMap.get(i.getParkId() + "").getValue());return i;}).collect(Collectors.toList());return newParks;}
以上代码为由近到远获取距离最近的停车场列表的示例方法,仅供参考