干翻全岛蛙蛙 发表于 2025-3-14 20:53:44

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

目录
 一、实现大抵思绪
        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()中,返回数据前完成对匹配度的计算
先看效果:司机端和乘客端能够看到顺路的车主和乘客以及二者直接的匹配度
https://i-blog.csdnimg.cn/direct/765fd58b79c84d0cadab916e811c316a.pnghttps://i-blog.csdnimg.cn/direct/f56981ee86794fc5b35962f73b5761ed.png
 
https://i-blog.csdnimg.cn/direct/768c969c9f664993970ea141459163fc.png 
 
 一、实现大抵思绪

        1.1引入GEO
           

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


        GEOADD:我们使用它来添加位置坐标信息 GEOADD key 经度1 纬度1 位置名1 经度2 纬度2 位置名2...
https://i-blog.csdnimg.cn/direct/b0cad87ea40f4052992ab45d6a1a2772.png
        
        GEODIST(distance):见名之意,计算两个地理位置的间隔 GEODIST key 位置一 位置二 m/km(不选择默认返回m(单位米)
https://i-blog.csdnimg.cn/direct/6066b98e3c364a9eb578e8f73002b2a3.png
        GEOHASH:将经纬度转换为hash存储(这两者转换redis使用了特殊的算法,不深究)这样存储更方便且节流内存
        
https://i-blog.csdnimg.cn/direct/f0eba572882c400498ebc6bec4f57f82.png
        GEORADIUS:本次讲解使用的方法为GEORADIUS,推荐大家使用GEOSEARCH,与GEORADIUS无太大差别,6.2版本GEORADIUS被弃用
https://i-blog.csdnimg.cn/direct/9567d745f99644228e5ef0eecd6dd425.png
        GEORADIUS key 经度 纬度(以该位置为圆心画圆)radius(半径) M/KM/FI/MI(半径单位)
        withcoord (返回范围内点到圆心的 经纬度坐标)
        withDIST(返回范围内点到圆心的近间隔) WITHASH(一般不使用)
        ASC(升序)DESC(降序)(如果不选择默认升序)
https://i-blog.csdnimg.cn/direct/37e443dd6f91436186ec448de975ad42.png
https://i-blog.csdnimg.cn/direct/0789033992944b77b53b66ffa6b9f8e0.png
        GEOSEARCH:
https://i-blog.csdnimg.cn/direct/cbcad95a5bc9477494fb629d533a8a1a.png
        官方文档位置: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实现匹配 (核心源码)

public Map<String, GeoBO> geoNearByXY(String prefix, String key, float lng, float lat)
 
public Map<String, GeoBO> geoNearByXY(String prefix, String key, float lng, float lat)
{      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)

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


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

        添加Geo数据存入redis;举行行程匹配;纪录符合条件行程
/**
   * 初始化GEO数据
   *
   * @param strokePO
   */
    private Collection<HitchGeoBO> initGeoData(StrokePO strokePO) {
      //添加GEO数据
      publishGeoData(strokePO);
      //筛选出来对应的数据
      Collection<HitchGeoBO> hitchGeoBOList = geoFilterMatch(strokePO);
      //记录所有行程
      for (HitchGeoBO hitchGeoBO : hitchGeoBOList) {
            //所有行程记录我
            redisHelper.addZset(HtichConstants.STROKE_GEO_ZSET_PREFIX, hitchGeoBO.getTargetId(), strokePO.getId(), getScore(hitchGeoBO));
            redisHelper.addHash(HtichConstants.STROKE_GEO_DISTANCE_PREFIX, hitchGeoBO.getTargetId(), strokePO.getId(), getDistanceStr(hitchGeoBO));
            //我记录所有行程
            redisHelper.addZset(HtichConstants.STROKE_GEO_ZSET_PREFIX, strokePO.getId(), hitchGeoBO.getTargetId(), getScore(hitchGeoBO));
            redisHelper.addHash(HtichConstants.STROKE_GEO_DISTANCE_PREFIX, strokePO.getId(), hitchGeoBO.getTargetId(), getDistanceStr(hitchGeoBO));
      }
      return hitchGeoBOList;
    }         3.3添加GEO数据         publishGeoData

/**
   * 添加GEO数据
   *
   * @param strokePO
   */
    private void publishGeoData(StrokePO strokePO) {
      //乘客/司机开始key 乘客筛选司机 。司机筛选乘客
      String start_key = strokePO.getRole() == 0 ? HtichConstants.STROKE_PASSENGER_GEO_START : HtichConstants.STROKE_DRIVER_GEO_START;
      //乘客/司机结束key
      String end_key = strokePO.getRole() == 0 ? HtichConstants.STROKE_PASSENGER_GEO_END : HtichConstants.STROKE_DRIVER_GEO_END;
      //添加开始结束的GEO信息
      redisHelper.addGEO(start_key, "", strokePO.getStartGeoLng(), strokePO.getStartGeoLat(), strokePO.getId());
      redisHelper.addGEO(end_key, "", strokePO.getEndGeoLng(), strokePO.getEndGeoLat(), strokePO.getId());
    }         3.4行程匹配

        实现行程匹配的逻辑
private Collection<HitchGeoBO> geoFilterMatch(StrokePO strokePO) {
      //乘客/司机开始key 乘客筛选司机 。司机筛选乘客
      String start_key = strokePO.getRole() == 0 ? HtichConstants.STROKE_DRIVER_GEO_START : HtichConstants.STROKE_PASSENGER_GEO_START;
      //乘客/司机结束key
      String end_key = strokePO.getRole() == 0 ? HtichConstants.STROKE_DRIVER_GEO_END : HtichConstants.STROKE_PASSENGER_GEO_END;
      //开始geo数据
      Map<String, GeoBO> startGeoBOMap = redisHelper.geoNearByXY(start_key, "", Float.parseFloat(strokePO.getStartGeoLng()), Float.parseFloat(strokePO.getStartGeoLat()));

      //结束的geo数据
      Map<String, GeoBO> endGeoBOMap = redisHelper.geoNearByXY(end_key, "", Float.parseFloat(strokePO.getEndGeoLng()), Float.parseFloat(strokePO.getEndGeoLat()));
      //定义map key的set
      Set<String> startSet = startGeoBOMap.keySet(), endSet = endGeoBOMap.keySet();
      //计算交集, 开始集合以及结束集合都包含的数据
      Collection<String> selectKeys = CollectionUtils.intersection(startSet, endSet);
      List<HitchGeoBO> hitchGeoBOList = new ArrayList<>();
      for (String key : selectKeys) {
            hitchGeoBOList.add(new HitchGeoBO(key, startGeoBOMap.get(key), endGeoBOMap.get(key)));
      }
      return hitchGeoBOList;
    }         3.5GEORADIUS具体实现

public Map<String, GeoBO> geoNearByXY(String prefix, String key, float lng, float lat)
{      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()中,返回数据前完成对匹配度的计算

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

  
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 如何使用RedisGEO实现行程匹配功能(含匹配度)