如何使用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]