目录
一、实现大抵思绪
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实现匹配 (核心源码)
- 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企服之家,中国第一个企服评测及商务社交产业平台。 |