Redis GeoHash 的一个小示例

栏目: 数据库 · 发布时间: 6年前

内容简介:上周产品经理提了一个类似于 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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Think About Algorithms

How to Think About Algorithms

Jeff Edmonds / Cambridge University Press / 2008-05-19 / USD 38.99

HOW TO THINK ABOUT ALGORITHMS There are many algorithm texts that provide lots of well-polished code and proofs of correctness. Instead, this one presents insights, notations, and analogies t......一起来看看 《How to Think About Algorithms》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具