如何使用RedisGEO实现行程匹配功能(含匹配度)

打印 上一主题 下一主题

主题 987|帖子 987|积分 2961

目录
 一、实现大抵思绪
        1.1引入GEO           
        1.2GEO的使用
        1.3大抵实现思绪:
二、结合对应的函数实现扼要流程分析(重要步骤标号,可直接看重要步骤)
        2.1起首乘客/司机发布行程
        2.2保存GEO (初始化GEO)(到此方法行程匹配逻辑已经执行竣事)
        2.3添加Geo数据              private void publishGeoData(StrokePO strokePO)
       2.4Geo筛选匹配(行程匹配)
       2.5通过GEORADIUS实现匹配 (核心源码)
        2.6终极返回给        initGeoData(StrokePO strokePO)
 三、实现的具体流程对应的源码
        3.2initGeoData
        3.3添加GEO数据         publishGeoData
        3.4行程匹配
        3.5GEORADIUS具体实现
        3.6终极在initGeoData()中,返回数据前完成对匹配度的计算

先看效果:司机端和乘客端能够看到顺路的车主和乘客以及二者直接的匹配度

 
 
 
 一、实现大抵思绪

        1.1引入GEO
           


                要实现行程匹配,那我们肯定要知道乘客/司机的位置坐标,从而举行计算和匹配。在Redis中,GEO这个数据类型能够很好的满足我们的需求,先来相识GEO数据布局。(黑马redis课程有一节课专门讲解)        

      
        1.2GEO的使用


        GEOADD:我们使用它来添加位置坐标信息 GEOADD key 经度1 纬度1 位置名1 经度2 纬度2 位置名2...

        
        GEODIST(distance):见名之意,计算两个地理位置的间隔 GEODIST key 位置一 位置二 m/km(不选择默认返回m(单位米)

        GEOHASH:将经纬度转换为hash存储(这两者转换redis使用了特殊的算法,不深究)这样存储更方便且节流内存
        

        GEORADIUS:本次讲解使用的方法为GEORADIUS,推荐大家使用GEOSEARCH,与GEORADIUS无太大差别,6.2版本GEORADIUS被弃用

        GEORADIUS key 经度 纬度(以该位置为圆心画圆)radius(半径) M/KM/FI/MI(半径单位)
        withcoord (返回范围内点到圆心的 经纬度坐标)
        withDIST(返回范围内点到圆心的近间隔) WITHASH(一般不使用)
        ASC(升序)DESC(降序)(如果不选择默认升序)


        GEOSEARCH:

        官方文档位置:Redis官方使用说明文档


        1.3大抵实现思绪:


 

        当乘客/司机发布行程时,会将自己的位置坐标存入GEO,使用GEORADIUS举行计算,选择范围内符合的乘客/司机,将返回值存入GEO封装(zset可计算分数,实现匹配度计算)
        封装后,根据乘客/司机 出发点 与 司机/乘客 出发点 计算间隔
                   乘客/司机 终点 与 司机/乘客 终点 计算间隔 加权均匀分后返回一个值(这个值就是zset中的score,即行程匹配度)
        将符合要求的,匹配度从高到低返回给满足条件的 司机/乘客
        到此,使用RedisGEO实现行程匹配功能(含匹配度)完成!
 

二、结合对应的函数实现扼要流程分析(重要步骤标号,可直接看重要步骤)


 
 
        我们直接来看后端实现的方法:
        2.1起首乘客/司机发布行程

        public ResponseVO<StrokeVO> publish(StrokeVO strokeVO)
        publish:


  • 身份校验
  • 将行程存入数据库
  • 初始化GEO(获取当前经纬度,存入redis)Collection<HitchGeoBO> collection = initGeoData(tmp);
  • 将collection(GEO坐标)存入数据库
        2.2保存GEO (初始化GEO)(到此方法行程匹配逻辑已经执行竣事)

        private Collection<HitchGeoBO> initGeoData(StrokePO strokePO)
        initGeoData:


  • 添加Geo数据                                     publishGeoData(strokePO)
  • 筛选出对应的数据(举行行程匹配) geoFilterMatch(strokePO)
  • 纪录所有符合条件的行程
  • 所有符合条件的行程纪录我(司机与乘客之间是相互选择的)
 
        2.3添加Geo数据              private void publishGeoData(StrokePO strokePO)



  •         判断该角色是司机/乘客,选择不同的key
  •         将该角色的 经纬度坐标和角色id(标识)存入redis(GEOADD)
        
       2.4Geo筛选匹配(行程匹配)



  •         判断该角色是司机/乘客,选择不同的出发点key和终点key
  •         出发点匹配出发点 public Map<String, GeoBO> geoNearByXY( prefix,  key,  lng,  lat)
  •         终点匹配终点 public Map<String, GeoBO> geoNearByXY( prefix,  key,  lng,  lat)
  •         在出发点和终点都举行匹配后,返回出发点和终点都符合条件的乘客/司机(取交集,肯定得出发点终点都离得近才方便司机送人,生活知识)
       2.5通过GEORADIUS实现匹配 (核心源码)

  1. public Map<String, GeoBO> geoNearByXY(String prefix, String key, float lng, float lat)
复制代码
 
  1. public Map<String, GeoBO> geoNearByXY(String prefix, String key, float lng, float lat)
  2. {        String redisKey = getRedisKey(prefix, key);        //以当前坐标为中心画圆        Circle circle = new Circle(                new Point(lng, lat),                new Distance(HtichConstants.STROKE_DIAMETER_RANGE, Metrics.KILOMETERS)        );        //构建一个查询 Redis 地理位置数据的请求,        //返回查询点周围最多 20 个位置的名称、坐标和它们到查询点的间隔,效果按间隔升序排列        RedisGeoCommands.GeoRadiusCommandArgs args =                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()                .includeDistance().includeCoordinates()                .sortAscending().limit(20);        //查找这个范围内的行程点        GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()                .radius(redisKey, circle, args);        return geoResultPack(results);    }
复制代码
 
 
        2.6终极返回给        initGeoData(StrokePO strokePO)

        末了一步:调用分数计算规则,根据分数举行排序后将所有行程返回给前端
  1. /**
  2.      * 计算分值
  3.      *
  4.      * @param hitchGeoBO
  5.      * @return
  6.      */
  7.     private float getScore(HitchGeoBO hitchGeoBO) {
  8.         return ((float) (1 - (hitchGeoBO.getStartGeo().getDistance() * 0.5 + hitchGeoBO.getEndGeo().getDistance() * 0.5) / HtichConstants.STROKE_DIAMETER_RANGE)) * 100;
  9.     }
复制代码
        到此实现完成!!!
     
 三、实现的具体流程对应的源码


        3.1发布行程
        StrokeVO中包含角色的信息和位置信息
        身份校验;入库保留纪录;初始化Geo数据;封装Geo;返回给前端
  1. /**
  2.      * 发布行程
  3.      *
  4.      * @return
  5.      */
  6.     public ResponseVO<StrokeVO> publish(StrokeVO strokeVO) {
  7.         AccountPO accountPO = accountAPIService.getAccountByID(RequestUtils.getCurrentUserId());
  8.         if (null == accountPO) {
  9.             throw new BusinessRuntimeException(BusinessErrors.DATA_NOT_EXIST, "发布人不存在");
  10.         }
  11.         if (strokeVO.getRole() == 1 && accountPO.getRole() == 0) {
  12.             throw new BusinessRuntimeException(BusinessErrors.AUTHENTICATION_ERROR, "请先认证为车主");
  13.         }
  14.         StrokePO strokePO = CommonsUtils.toPO(strokeVO);
  15.         //入mysql库
  16.         StrokePO tmp = strokeAPIService.publish(strokePO);
  17.         Collection<HitchGeoBO> collection = initGeoData(tmp);
  18.         for (HitchGeoBO hitchGeoBO : collection) {
  19.             WorldMapBO worldMapBO = new WorldMapBO(hitchGeoBO.getStartGeo(), hitchGeoBO.getTargetId());
  20.             sendStartGeo(worldMapBO);
  21.         }
  22.         return ResponseVO.success(tmp);
  23.     }
复制代码
        3.2initGeoData

        添加Geo数据存入redis;举行行程匹配;纪录符合条件行程
  1. /**
  2.      * 初始化GEO数据
  3.      *
  4.      * @param strokePO
  5.      */
  6.     private Collection<HitchGeoBO> initGeoData(StrokePO strokePO) {
  7.         //添加GEO数据
  8.         publishGeoData(strokePO);
  9.         //筛选出来对应的数据
  10.         Collection<HitchGeoBO> hitchGeoBOList = geoFilterMatch(strokePO);
  11.         //记录所有行程
  12.         for (HitchGeoBO hitchGeoBO : hitchGeoBOList) {
  13.             //所有行程记录我
  14.             redisHelper.addZset(HtichConstants.STROKE_GEO_ZSET_PREFIX, hitchGeoBO.getTargetId(), strokePO.getId(), getScore(hitchGeoBO));
  15.             redisHelper.addHash(HtichConstants.STROKE_GEO_DISTANCE_PREFIX, hitchGeoBO.getTargetId(), strokePO.getId(), getDistanceStr(hitchGeoBO));
  16.             //我记录所有行程
  17.             redisHelper.addZset(HtichConstants.STROKE_GEO_ZSET_PREFIX, strokePO.getId(), hitchGeoBO.getTargetId(), getScore(hitchGeoBO));
  18.             redisHelper.addHash(HtichConstants.STROKE_GEO_DISTANCE_PREFIX, strokePO.getId(), hitchGeoBO.getTargetId(), getDistanceStr(hitchGeoBO));
  19.         }
  20.         return hitchGeoBOList;
  21.     }
复制代码
        3.3添加GEO数据         publishGeoData

  1. /**
  2.      * 添加GEO数据
  3.      *
  4.      * @param strokePO
  5.      */
  6.     private void publishGeoData(StrokePO strokePO) {
  7.         //乘客/司机开始key 乘客筛选司机 。司机筛选乘客
  8.         String start_key = strokePO.getRole() == 0 ? HtichConstants.STROKE_PASSENGER_GEO_START : HtichConstants.STROKE_DRIVER_GEO_START;
  9.         //乘客/司机结束key
  10.         String end_key = strokePO.getRole() == 0 ? HtichConstants.STROKE_PASSENGER_GEO_END : HtichConstants.STROKE_DRIVER_GEO_END;
  11.         //添加开始结束的GEO信息
  12.         redisHelper.addGEO(start_key, "", strokePO.getStartGeoLng(), strokePO.getStartGeoLat(), strokePO.getId());
  13.         redisHelper.addGEO(end_key, "", strokePO.getEndGeoLng(), strokePO.getEndGeoLat(), strokePO.getId());
  14.     }
复制代码
        3.4行程匹配

        实现行程匹配的逻辑
  1. private Collection<HitchGeoBO> geoFilterMatch(StrokePO strokePO) {
  2.         //乘客/司机开始key 乘客筛选司机 。司机筛选乘客
  3.         String start_key = strokePO.getRole() == 0 ? HtichConstants.STROKE_DRIVER_GEO_START : HtichConstants.STROKE_PASSENGER_GEO_START;
  4.         //乘客/司机结束key
  5.         String end_key = strokePO.getRole() == 0 ? HtichConstants.STROKE_DRIVER_GEO_END : HtichConstants.STROKE_PASSENGER_GEO_END;
  6.         //开始geo数据
  7.         Map<String, GeoBO> startGeoBOMap = redisHelper.geoNearByXY(start_key, "", Float.parseFloat(strokePO.getStartGeoLng()), Float.parseFloat(strokePO.getStartGeoLat()));
  8.         //结束的geo数据
  9.         Map<String, GeoBO> endGeoBOMap = redisHelper.geoNearByXY(end_key, "", Float.parseFloat(strokePO.getEndGeoLng()), Float.parseFloat(strokePO.getEndGeoLat()));
  10.         //定义map key的set
  11.         Set<String> startSet = startGeoBOMap.keySet(), endSet = endGeoBOMap.keySet();
  12.         //计算交集, 开始集合以及结束集合都包含的数据
  13.         Collection<String> selectKeys = CollectionUtils.intersection(startSet, endSet);
  14.         List<HitchGeoBO> hitchGeoBOList = new ArrayList<>();
  15.         for (String key : selectKeys) {
  16.             hitchGeoBOList.add(new HitchGeoBO(key, startGeoBOMap.get(key), endGeoBOMap.get(key)));
  17.         }
  18.         return hitchGeoBOList;
  19.     }
复制代码
        3.5GEORADIUS具体实现

  1. public Map<String, GeoBO> geoNearByXY(String prefix, String key, float lng, float lat)
  2. {        String redisKey = getRedisKey(prefix, key);        //以当前坐标为中心画圆        Circle circle = new Circle(                new Point(lng, lat),                new Distance(HtichConstants.STROKE_DIAMETER_RANGE, Metrics.KILOMETERS)        );        //限制20条,可以根据实际情况调解        RedisGeoCommands.GeoRadiusCommandArgs args =                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()                .includeDistance().includeCoordinates()                .sortAscending().limit(20);        //查找这个范围内的行程点        GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()                .radius(redisKey, circle, args);        return geoResultPack(results);    }
复制代码
 
        3.6终极在initGeoData()中,返回数据前完成对匹配度的计算

        加权均匀分实现
  1. /**
  2.      * 计算分值
  3.      *
  4.      * @param hitchGeoBO
  5.      * @return
  6.      */
  7.     private float getScore(HitchGeoBO hitchGeoBO) {
  8.         return ((float) (1 - (hitchGeoBO.getStartGeo().getDistance() * 0.5 + hitchGeoBO.getEndGeo().getDistance() * 0.5) / HtichConstants.STROKE_DIAMETER_RANGE)) * 100;
  9.     }
复制代码
                
        至此全部竣事,学习完成!!!争取过几天自己实现一个!!!到时候再纪录一下。阅读源码对我们提升自己的资助还是很大的,一开始静不下心没好悦目,进入状态之后还是可以看懂的,狠狠提升自己!!!球球义父们点赞,收藏,球球了!!!!!!

  
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

干翻全岛蛙蛙

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表