4种 LBS “附近的人” 实现方案,人人都能看的懂
来源:SegmentFault
时间:2023-02-23 14:18:57 260浏览 收藏
哈喽!今天心血来潮给大家带来了《4种 LBS “附近的人” 实现方案,人人都能看的懂》,想必大家应该对数据库都不陌生吧,那么阅读本文就都不会很困难,以下内容主要涉及到MySQL、Redis、mongodb、java12,若是你正在学习数据库,千万别错过这篇文章~希望能帮助到你!
本文收录在个人博客:http://www.chengxy-nds.top,技术资料共享,一起进步学习
昨天一位公众号粉丝和我讨论了一道面试题,个人觉得比较有意义,这里整理了一下分享给大家,愿小伙伴们面试路上少踩坑。面试题目比较简单:“让你实现一个附近的人功能,你有什么方案?”,这道题其实主要还是考察大家对于技术的广度,本文介绍几种方案,给大家一点思路,避免在面试过程中语塞而影响面试结果,如有不严谨之处,还望亲人们温柔指正!
CREATE TABLE `nearby_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL COMMENT '名称', `longitude` double DEFAULT NULL COMMENT '经度', `latitude` double DEFAULT NULL COMMENT '纬度', `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
计算两个点之间的距离,用了一个三方的类库,毕竟自己造的轮子不是特别圆,还有可能是方的,啊哈哈哈~
com.spatial4j spatial4j 0.5
获取到外接正方形后,以正方形的最大最小经、纬度值搜索正方形区域内的用户,再剔除超过指定距离的用户,就是最终的
private SpatialContext spatialContext = SpatialContext.GEO; /** * 获取附近 x 米的人 * * @param distance 搜索距离范围 单位km * @param userLng 当前用户的经度 * @param userLat 当前用户的纬度 */ @GetMapping("/nearby") public String nearBySearch(@RequestParam("distance") double distance, @RequestParam("userLng") double userLng, @RequestParam("userLat") double userLat) { //1.获取外接正方形 Rectangle rectangle = getRectangle(distance, userLng, userLat); //2.获取位置在正方形内的所有用户 Listusers = userMapper.selectUser(rectangle.getMinX(), rectangle.getMaxX(), rectangle.getMinY(), rectangle.getMaxY()); //3.剔除半径超过指定距离的多余用户 users = users.stream() .filter(a -> getDistance(a.getLongitude(), a.getLatitude(), userLng, userLat)
由于用户间距离的排序是在业务代码中实现的,可以看到SQL语句也非常的简单。
四、Mysql + GeoHash
1、设计思路
这种方式的设计思路更简单,在存用户位置信息时,根据用户经、纬度属性计算出相应的
CREATE TABLE `nearby_user_geohash` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL COMMENT '名称', `longitude` double DEFAULT NULL COMMENT '经度', `latitude` double DEFAULT NULL COMMENT '纬度', `geo_code` varchar(64) DEFAULT NULL COMMENT '经纬度所计算的geohash码', `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), KEY `index_geo_hash` (`geo_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
首先根据用户经、纬度信息,在指定精度后计算用户坐标的
private SpatialContext spatialContext = SpatialContext.GEO; /*** * 添加用户 * @return */ @PostMapping("/addUser") public boolean add(@RequestBody UserGeohash user) { //默认精度12位 String geoHashCode = GeohashUtils.encodeLatLon(user.getLatitude(),user.getLongitude()); return userGeohashService.save(user.setGeoCode(geoHashCode).setCreateTime(LocalDateTime.now())); } /** * 获取附近指定范围的人 * * @param distance 距离范围(附近多远的用户) 单位km * @param len geoHash的精度(几位的字符串) * @param userLng 当前用户的经度 * @param userLat 当前用户的纬度 * @return json */ @GetMapping("/nearby") public String nearBySearch(@RequestParam("distance") double distance, @RequestParam("len") int len, @RequestParam("userLng") double userLng, @RequestParam("userLat") double userLat) { //1.根据要求的范围,确定geoHash码的精度,获取到当前用户坐标的geoHash码 GeoHash geoHash = GeoHash.withCharacterPrecision(userLat, userLng, len); //2.获取到用户周边8个方位的geoHash码 GeoHash[] adjacent = geoHash.getAdjacent(); QueryWrapperqueryWrapper = new QueryWrapper () .likeRight("geo_code",geoHash.toBase32()); Stream.of(adjacent).forEach(a -> queryWrapper.or().likeRight("geo_code",a.toBase32())); //3.匹配指定精度的geoHash码 List users = userGeohashService.list(queryWrapper); //4.过滤超出距离的 users = users.stream() .filter(a ->getDistance(a.getLongitude(),a.getLatitude(),userLng,userLat)
五、Redis + GeoHash
GEOADD key longitude latitude member [longitude latitude member ...]
其中,
GEOADD hotel 119.98866180732716 30.27465803229662 火锅店
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] [STORE key] [STORedisT key]
范围单位:
GEORADIUS hotel 119.98866180732716 30.27465803229662 500 m WITHCOORD
@Autowired private RedisTemplateredisTemplate; //GEO相关命令用到的KEY private final static String KEY = "user_info"; public boolean save(User user) { Long flag = redisTemplate.opsForGeo().add(KEY, new RedisGeoCommands.GeoLocation( user.getName(), new Point(user.getLongitude(), user.getLatitude())) ); return flag != null && flag > 0; } /** * 根据当前位置获取附近指定范围内的用户 * @param distance 指定范围 单位km ,可根据{@link org.springframework.data.geo.Metrics} 进行设置 * @param userLng 用户经度 * @param userLat 用户纬度 * @return */ public String nearBySearch(double distance, double userLng, double userLat) { List users = new ArrayList(); // 1.GEORADIUS获取附近范围内的信息 GeoResults > reslut = redisTemplate.opsForGeo().radius(KEY, new Circle(new Point(userLng, userLat), new Distance(distance, Metrics.KILOMETERS)), RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs() .includeDistance() .includeCoordinates().sortAscending()); //2.收集信息,存入list List >> content = reslut.getContent(); //3.过滤掉超过距离的数据 content.forEach(a-> users.add( new User().setDistance(a.getDistance().getValue()) .setLatitude(a.getContent().getPoint().getX()) .setLongitude(a.getContent().getPoint().getY()))); return JSON.toJSONString(users); }
六、MongoDB + 2d索引
1、设计思路
db.hotel.insertMany([ {'name':'hotel1', location:[115.993121,28.676436]}, {'name':'hotel2', location:[116.000093,28.679402]}, {'name':'hotel3', location:[115.999967,28.679743]}, {'name':'hotel4', location:[115.995593,28.681632]}, {'name':'hotel5', location:[115.975543,28.679509]}, {'name':'hotel6', location:[115.968428,28.669368]}, {'name':'hotel7', location:[116.035262,28.677037]}, {'name':'hotel8', location:[116.024770,28.68667]}, {'name':'hotel9', location:[116.002384,28.683865]}, {'name':'hotel10', location:[116.000821,28.68129]}, ])
接下来我们给
db.coll.createIndex({'location':"2d"}, {"bits":11111})
用
db.hotel.aggregate({ $geoNear:{ near: [115.999567,28.681813], // 当前坐标 spherical: true, // 计算球面距离 distanceMultiplier: 6378137, // 地球半径,单位是米,那么的除的记录也是米 maxDistance: 2000/6378137, // 过滤条件2000米内,需要弧度 distanceField: "distance" // 距离字段别名 } })
看到结果中有符合条件的数据,还多出一个字段
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e58"), "name" : "hotel10", "location" : [ 116.000821, 28.68129 ], "distance" : 135.60095397487655 } { "_id" : ObjectId("5e96a5c91b8d4ce765381e51"), "name" : "hotel3", "location" : [ 115.999967, 28.679743 ], "distance" : 233.71915803517447 } { "_id" : ObjectId("5e96a5c91b8d4ce765381e50"), "name" : "hotel2", "location" : [ 116.000093, 28.679402 ], "distance" : 273.26317035334176 } { "_id" : ObjectId("5e96a5c91b8d4ce765381e57"), "name" : "hotel9", "location" : [ 116.002384, 28.683865 ], "distance" : 357.5791936927476 } { "_id" : ObjectId("5e96a5c91b8d4ce765381e52"), "name" : "hotel4", "location" : [ 115.995593, 28.681632 ], "distance" : 388.62555058249967 } { "_id" : ObjectId("5e96a5c91b8d4ce765381e4f"), "name" : "hotel1", "location" : [ 115.993121, 28.676436 ], "distance" : 868.6740526419927 }
总结
本文重点并不是在具体实现,旨在给大家提供一些设计思路,面试中可能你对某一项技术了解的并不深入,但如果你的知识面宽,可以从多方面说出多种设计的思路,能够侃侃而谈,那么会给面试官极大的好感度,拿到offer的概率就会高很多。而且
“附近的人”功能使用的场景比较多,尤其是像电商平台应用更为广泛,所以想要进大厂的同学,这类的知识点还是应该有所了解的。
代码实现借鉴了一位大佬的开源项目,这里有前三种实现方式的demo,感兴趣的小伙伴可以学习一下,GitHub地址:
https://github.com/larscheng/larscheng-learning-demo/tree/master/NearbySearch,。
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于数据库的相关知识,也可关注golang学习网公众号。
-
499 收藏
-
286 收藏
-
244 收藏
-
235 收藏
-
157 收藏
-
368 收藏
-
475 收藏
-
266 收藏
-
273 收藏
-
283 收藏
-
210 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 大气的纸鹤
- 好细啊,收藏了,感谢up主的这篇文章,我会继续支持!
- 2023-06-02 16:16:06
-
- 谨慎的钥匙
- 这篇博文真是及时雨啊,太全面了,感谢大佬分享,已收藏,关注楼主了!希望楼主能多写数据库相关的文章。
- 2023-05-10 10:35:25
-
- 执着的百褶裙
- 赞 👍👍,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢老哥分享博文!
- 2023-03-04 00:52:30
-
- 典雅的大地
- 这篇文章真是及时雨啊,很详细,赞 👍👍,已收藏,关注楼主了!希望楼主能多写数据库相关的文章。
- 2023-02-27 07:24:27
-
- 土豪的航空
- 感谢大佬分享,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢老哥分享技术贴!
- 2023-02-23 19:42:03