内容简介:上周产品经理提了一个类似于 LBS 的应用,第一时间想到了忘记了之前什么时候看 Redis 的 API,发现 Redis 自 3.2 版本之后,新增了一类关于地理位置相关的 API,于是拿来测试一下,发现特别好用,写一个小例子作为笔记。首先需要说明的是,由于我们公司的 JDK 的版本是 1.7,所以我采用的 spring-data-redis 的版本是:1.8.20.RELEASE,最新二点几的版本已经不支持 JDK 1.7,而一点几和二点几的版本的 API 有略微的差异(下面会说明,还有一点点我的小感悟)
上周产品经理提了一个类似于 LBS 的应用,第一时间想到了忘记了之前什么时候看 Redis 的 API,发现 Redis 自 3.2 版本之后,新增了一类关于地理位置相关的 API,于是拿来测试一下,发现特别好用,写一个小例子作为笔记。
首先需要说明的是,由于我们公司的 JDK 的版本是 1.7,所以我采用的 spring-data-redis 的版本是:1.8.20.RELEASE,最新二点几的版本已经不支持 JDK 1.7,而一点几和二点几的版本的 API 有略微的差异(下面会说明,还有一点点我的小感悟),废话不多说,直接看例子:
@Override public Long geoAdd(String key, List<Entity> entities) { redisTemplate.delete(key); GeoOperations geoOperations = redisTemplate.opsForGeo(); Map<String, Point> map = new HashMap<>(); Point point = null; for (Entity entity : entities) { point = new Point(entity.getLongitude(), entity.getLatitude()); map.put(gson.toJson(entity), point); } Long add = geoOperations.geoAdd(key, map); return add; }
参数就两个很简单,一个是 key,一个是数据集,我们将在这个集合中找出符合条件的数据,需要说明的是:
1. 第一行我先调用了一个 delete 方法,是将上次放进去的数据删除,因为这个命令是 add,也就是新增,但是 redis 并没有提供直接删除这个 key 的命令(有一个 remove 的方法,但是需要传入删除哪些数据,也就是不能只给一个 key,把这个 key 对应的数据都删除,个人感觉不太好用),当然你也可以在计算后取得相应的数据之后删除,个人感觉都一样,不要忘记清理数据就行,另一个方法就是设置过期时间,也都行;
2. 为什么要清理数据?因为这个 add,不同的情况,放进去的数据应该不同的,如果 entities 已经发生变更,而一直 add,那么数据将会乱掉,所以先把之前的数据删掉再说;
3. 我个人采用的是把数据放到了 Map 中,其中 key 是对象序列化之后的 json 串,目的是为了下面找到对应的数据之后,直接反序列化成对象进行返回,当然也可以采用其他的方案,还有就是 add 还有一些其他的 API,这个大家可以自己看文档,选择合适的就行;
数据放进去之后就是计算了,我们的需求就是算一个人旁边几公里内有多少符合条件的数据,代码如下:
@Override public Page<Entity> geoRadius(String key, Double latitude, Double longitude, Integer distance, String sort, Integer pageNo, Integer pageSize) { GeoOperations geoOperations = redisTemplate.opsForGeo(); Circle circle = new Circle(new Point(latitude, longitude), new Distance(distance / 1000, RedisGeoCommands.DistanceUnit.KILOMETERS)); RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs(); geoRadiusCommandArgs = geoRadiusCommandArgs.includeCoordinates().includeDistance(); if ("DESC".equals(sort)) { geoRadiusCommandArgs.sortDescending(); } else { geoRadiusCommandArgs.sortAscending(); } GeoResults<RedisGeoCommands.GeoLocation<String>> radiusGeo = geoOperations.geoRadius(key, circle, geoRadiusCommandArgs); List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = radiusGeo.getContent(); List<Entity> entities = null; Integer size = null; if (CollectionUtils.isNotEmpty(list)) { int limit = pageNo * pageSize; size = list.size(); if (limit > size) { limit = size; } list = list.subList((pageNo - 1) * pageSize, limit); entities = new ArrayList<>(pageSize); for (GeoResult<RedisGeoCommands.GeoLocation<String>> geoLocationGeoResult : list) { RedisGeoCommands.GeoLocation<String> geoLocationGeoResultContent = geoLocationGeoResult.getContent(); Distance distance1 = geoLocationGeoResult.getDistance(); double value = distance1.getValue(); value = (double) Math.round(value * 100) / 100; String name = geoLocationGeoResultContent.getName(); Entity entity = gson.fromJson(name, Entity.class); entity.setDistance(value); entities.add(entity); } } Page<entity> page = new Page<>(); page.setData(entities); page.setTotal(size); return page; }
这段代码需要说明的是:
1. 参数 key 就是之前放进去数据的 key(这不废话吗?),然后是当前人的经纬度,多远以内的数据,由近到远排序还是由远到近的 排序 方式,以及分页数据;
2. 例子中距离多远以内的,传进来的是米,但是后面返回的是千米,所以除以 1000 转了一次,根据业务而定;
3. 默认是正序,也就是由近到远,但是也支持由远到近
4. 如果仅仅是排序,也就是对所有的数据由远到近或者由近到远,那么距离就应该是无穷远,Integer.MAX_VALUE;
5. 有一个 geoRadiusCommandArgs.limit(); 方法,其实就是取 top N,应该挺常用的,但这次不适合我们的应用
6. list = list.subList((page – 1) * pageSize, limit); 的意思是说,只需要取 pageSize 个数据进行反序列化就好了,而上面那个判断,是为了防止最后一页下表越界;
7. value = (double) Math.round(value * 10) / 10; 数据距离中心点的距离,四舍五入,根据需求就好;
8. 就是对应的数据反序列化以及组装返回了
经过以上,就可以做一个基于 LBS 的应用了,很简单吧?需要补充说明的是:
1. redis 还有好几个其他的 GeoHash 相关的命令和 API,自行查询文档就好;
2. redis 这个算法,其实就是基于 GeoHash,关于这个算法已经实现,网上有很多资料,有比较详细的说明,感兴趣的自行查阅相关文档就好;
3. spring-data-redis 一点几和二点几,你说相关的 API 有区别,有什么区别啊?区别很简单就是一点几的方法,都是 geo 打头,例如 geoAdd、geoRadius 等,而二点几版本直接是:add、radius 等,其实我们根据命令 GeoOperations geoOperations = redisTemplate.opsForGeo(); 得到的肯定是操作 geo 相关的命令,这个时候方法再以 geo 打头不是有点画蛇添足吗?但是这个给我们什么提示呢?我们很多时候,根据 id 查询的是,不用写 entityService.getEntityById(),因为理论上这个时候我们肯定是查询 Entity,所以直接写 entityService.getById(),这就告诉我们命名的时候需要仔细斟酌,见名知意的情况下越短越好。
参考资料:
1. http://redisdoc.com/geo/index.html 官方 API 是最好的学习资料
2. https://mygodccl.iteye.com/blog/2374978
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数学与泛型编程
[美]亚历山大 A. 斯捷潘诺夫(Alexander A. Stepanov)、[美]丹尼尔 E. 罗斯(Daniel E. Rose) / 爱飞翔 / 机械工业出版社 / 2017-8 / 79
这是一本内容丰富而又通俗易懂的书籍,由优秀的软件设计师 Alexander A. Stepanov 与其同事 Daniel E. Rose 所撰写。作者在书中解释泛型编程的原则及其所依据的抽象数学概念,以帮助你写出简洁而强大的代码。 只要你对编程相当熟悉,并且擅长逻辑思考,那么就可以顺利阅读本书。Stepanov 与 Rose 会清晰地讲解相关的抽象代数及数论知识。他们首先解释数学家想要解决......一起来看看 《数学与泛型编程》 这本书的介绍吧!