附近的人?你zao吗?
前几天收到一个新的需求,需要实现类似“附近的人”的功能:根据自己当前的定位,获取距离范围内的所有任务地点。刚看到这个需求时有点懵逼,第一想到的就是要利用地球的半径公式去计算距离,也就是把地球想成一个球体,去计算球上两点之间的距离。可想而知,这样的方法效率会比较低,每条数据都要来与本人的坐标做计算,太过繁琐。经过大佬的指点,想到了用redis自带的GEO来实现此功能。
实战演习
以下是给大家准备的sql脚本
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for job_base_info
-- ----------------------------
DROP TABLE IF EXISTS `job_base_info`;
CREATE TABLE `job_base_info` (
`job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID',
`job_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务名称',
`job_location` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '任务地点位置经纬度-逗号隔开',
`job_detail_address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务详细地点位置',
PRIMARY KEY (`job_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '工作任务详情基础信息表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of job_base_info
-- ----------------------------
INSERT INTO `job_base_info` VALUES (1, '软件开发', '120.433576,36.139697', '青岛市崂山区海尔路1号');
INSERT INTO `job_base_info` VALUES (2, '儿童摄影', '120.420297,36.156589', '山东省青岛市李沧区书院路188号');
INSERT INTO `job_base_info` VALUES (3, '清洁家用电器', '120.025706,36.281478', '山东省青岛市胶州市福州支路232号东60米');
INSERT INTO `job_base_info` VALUES (4, '辩论学习', '120.505042,36.171247', '松岭路238号中国海洋大学内');
SET FOREIGN_KEY_CHECKS = 1;
废话不多说,让我们来看看具体的实现
(1)我们要在程序启动时,将数据库中的任务数据的坐标信息初始化到redis中(此处暂且忽略任务的增删改查对redis中数据的影响)
@PostConstruct
public void init(){
//首先要删除该key的所有值
redisTemplate.delete("company-task");
List<JobBaseInfo> jobBaseInfoList = jobBaseInfoMapper.selectList(Wrappers.emptyWrapper());
jobBaseInfoList.stream().forEach(item->{
String jobLocation = item.getJobLocation();
if(StrUtil.isNotEmpty(jobLocation)){
String[] split = jobLocation.split(",");comp
if(split.length==2){
//Point(经度, 纬度)
Point point = new Point(Double.parseDouble(split[0]),Double.parseDouble(split[1]));
//将经纬度数据及其id存到key为“company-task”中
redisTemplate.opsForGeo().add("company-task",point,item.getJobId());
}
}
});
}
(2)查询当前坐标下3km范围内的任务地点(外加根据任务名搜索的联合查询)
@Override
public List<JobBaseInfo> selectJobList(JobBaseInfoDTO jobBaseInfoDTO) {
String jobLocation = jobBaseInfoDTO.getJobLocation();
//距离
Double distance = jobBaseInfoDTO.getDistance();
List<Integer> idList = new ArrayList<>();
if(StringUtils.isNotNull(jobLocation) && StringUtils.isNotNull(distance)){
String[] split = jobLocation.split(",");
if(split.length==2){
//Point(经度, 纬度) Distance(距离量, 距离单位)
Circle circle = new Circle(new Point(Double.parseDouble(split[0]),Double.parseDouble(split[1])),
new Distance(distance, Metrics.KILOMETERS));
//params: key, Circle 获取存储到redis中的distance范围内的所有任务地点数据
GeoResults radius = redisTemplate.opsForGeo().radius("company-task", circle);
List<GeoResult> contentList = radius.getContent();
if(contentList.size()>0){
contentList.stream().forEach(item->{
RedisGeoCommands.GeoLocation content = (RedisGeoCommands.GeoLocation) item.getContent();
idList.add((Integer) content.getName());
});
}
}
}
jobBaseInfoDTO.setIdList(idList);
return jobBaseInfoMapper.selectJobList(jobBaseInfoDTO);
}
selectJobList(jobBaseInfoDTO)方法的sql如下
<select id="selectJobList" resultType="com.itzyq.redislikes.model.entity.JobBaseInfo">
select
<include refid="Base_Column_List"></include>
from job_base_info
<where>
<if test="jobName!=null and jobName!=''">
and job_name like CONCAT("%",#{jobName},"%")
</if>
<if test="idList!=null and idList.size > 0 ">
and job_id in
<foreach collection="idList" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
到这儿我们就已经实现了“附近的人”的功能了,接下来就让我们具体的了解一下Redis中的GEO都有哪些骚操作吧。gzh回复“pSearch”获取源码呦!
GEO操作
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增,GEO 是基于zset的一种扩展数据格式。Redis GEO 操作方法有:
- geoadd:添加地理位置的坐标。
- geopos:获取地理位置的坐标。
- geodist:计算两个位置之间的距离。
- georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
- georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
- geohash:返回一个或多个位置对象的 geohash 值。
1、geoadd
geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。
geoadd 语法格式如下:
GEOADD key longitude latitude member [longitude latitude member ...]
以下实例中 key 为 Sicily,Palermo 和 Catania 为位置名称 :
实例
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis>

2、geopos
geopos 用于从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。
geopos 语法格式如下:
GEOPOS key member [member ...]
实例
redis> GEOPOS Sicily Palermo Catania NonExisting
1) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
3) (nil)
redis>
注:也可以使用zrange返回所有的位置元素而不带经纬度信息
redis> ZRANGE Sicily 0 -1
1) "Palermo"
2) "Catania"
redis>
3、geodist
geodist 用于返回两个给定位置之间的距离。
geodist 语法格式如下:
GEODIST key member1 member2 [m|km|ft|mi]
member1 member2 为两个地理位置。
最后一个距离单位参数说明:
- m :米,默认单位。
- km :千米。
- mi :英里。
- ft :英尺。
实例: 计算 Palermo 与 Catania 之间的距离
redis> GEODIST Sicily Palermo Catania
"166274.1516"
redis> GEODIST Sicily Palermo Catania km
"166.2742"
redis> GEODIST Sicily Palermo Catania mi
"103.3182"
redis> GEODIST Sicily Foo Bar
(nil)
redis>
4、georadius、georadiusbymember
georadius 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
georadiusbymember 和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。
georadius 与 georadiusbymember 语法格式如下:
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
参数说明:
- m :米,默认单位。
- km :千米。
- mi :英里。
- ft :英尺。
- WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
- WITHCOORD: 将位置元素的经度和维度也一并返回。
- WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
- COUNT 限定返回的记录数。
- ASC: 查找结果根据距离从近到远排序。
- DESC: 查找结果根据从远到近排序。
georadius 实例
redis> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
2) "190.4424"
2) 1) "Catania"
2) "56.4413"
redis> GEORADIUS Sicily 15 37 200 km WITHCOORD
1) 1) "Palermo"
2) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis>
georadiusbymember 实例:
redis> GEOADD Sicily 13.583333 37.316667 "Agrigento"
(integer) 1
redis> GEORADIUSBYMEMBER Sicily Agrigento 100 km
1) "Agrigento"
2) "Palermo"
redis>
5、geohash
Redis GEO 使用 geohash 来保存地理位置的坐标。geohash 用于获取一个或多个位置元素的 geohash 值。
geohash 语法格式如下:
GEOHASH key member [member ...]
实例:
redis> GEOHASH Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"
redis>
geo并没有提供删除指令,但根据其底层是zset实现,我们可以使用zrem对数据进行删除
redis> ZREM Sicily Agrigento
"1"
redis>
Redis GEO JAVA API
有了以上GEO的操作,我们可以在java中找到对应的api
/**
* 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。
*/
//params: key, Point(经度, 纬度), 地方名称
Long addedNum = redisTemplate.opsForGeo().add("Sicily", new Point(13.361389,38.115556), "Palermo");
/**
* 从key里返回所有给定位置元素的位置(经度和纬度)。
*/
//params: key, 地方名称...
List<Point> points = redisTemplate.opsForGeo().position("Sicily","Palermo","Catania");
/**
* 返回两个给定位置之间的距离。
*/
//params: key, 地方名称1, 地方名称2, 距离单位
Distance distance = redisTemplate.opsForGeo()
.distance("Sicily","Palermo","Catania", RedisGeoCommands.DistanceUnit.KILOMETERS);
/**
* 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素,并给出所有位置元素与中心的平均距离。
*/
//Point(经度, 纬度) Distance(距离量, 距离单位)
Circle circle = new Circle(new Point(13.361389,38.115556), new Distance(200, Metrics.KILOMETERS));
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().
//包含距离,包含经纬度,升序前五个
includeDistance().includeCoordinates().sortAscending().limit(5);
//params: key, Circle, GeoRadiusCommandArgs
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius("Sicily",circle,args);
/**
* 以给定的城市为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素,并给出所有位置元素与中心的平均距离。
*/
//params: 距离量, 距离单位
Distance distance = new Distance(200,Metrics.KILOMETERS);
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance().includeCoordinates().sortAscending().limit(5);
//params: key, 地方名称, Circle, GeoRadiusCommandArgs
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius("Sicily","Palermo",distance,args);
/**
* 返回一个或多个位置元素的 Geohash 表示
*/
//params: key, 地方名称...
List<String> results = redisTemplate.opsForGeo()
.hash("Sicily","Palermo","Catania");
看到这里,“附近的人”功能你一定掌握了吧!如果你觉得这篇文章对你有所帮助,请移步关注gzh“阿Q说代码”或者直接联系阿Q:qingqing-4132,阿Q期待你的到来!
附近的人?你zao吗?的更多相关文章
- ZAO 换脸不安全?用 python 轻松实现 AI
最近两天一款名为 「ZAO」 的 App 刷爆了朋友圈,它的主打功能是 AI 换脸,宣称「只需一张照片,就能出演天下好戏」 : 现实中不能实现当明星的梦,在这个 App 里你可以,想演谁演谁.新鲜.好 ...
- 一个IT人的成长路
毕业四年多了,来深圳三年多了,经历了刚毕业的懵懂少年,成长为现在的成熟稳重青年.职场上,从刚毕业的小白,成长为现在可以成熟应对各种事情的老司机.经历过从初级研发工程师,到中级研发工程师,到高级研发工程 ...
- 通过自定义特性,使用EF6拦截器完成创建人、创建时间、更新人、更新时间的统一赋值(使用数据库服务器时间赋值,接上一篇)
目录: 前言 设计(完成扩展) 实现效果 扩展设计方案 扩展后代码结构 集思广益(问题) 前言: 在上一篇文章我写了如何重建IDbCommandTreeInterceptor来实现创建人.创建时间.更 ...
- BPM始终服务于人,落脚于人
数字经济时代下,云计算.大数据.移动互联已经成为当下企业必须采取的武装力量.随着互联网+.中国制造2025.工业4.0等国家战略的引导与支持,无数的企业在这场数字化浪潮中使尽浑身解数,想要抓住机遇奋力 ...
- 给缺少Python项目实战经验的人
我们在学习过程中最容易犯的一个错误就是:看的多动手的少,特别是对于一些项目的开发学习就更少了! 没有一个完整的项目开发过程,是不会对整个开发流程以及理论知识有牢固的认知的,对于怎样将所学的理论知识应用 ...
- 周末聊聊IT人员的人脉观:关于帮妹子找兼职有感
背景: 前几天,有个认识了好几年的网友,现在是大学生,在厦门读大一,说和她同学要一起到广州找兼职,看我有没有介绍. 像我这么积极热心善良的人,就说帮她找找看,结果问了几次,没消息,只好诚实的回复人家, ...
- wget 显示"英国中部时间",去掉烦人的刷屏显示
wget下载文件显示多行,进度条后面显示英国中部时间,非常让人郁闷. 本来英文是eta(Estimated Time of Arrival 预计到达时间),翻译错了,干脆去掉好了. 先要有两个个工具 ...
- 你真的会玩SQL吗?让人晕头转向的三值逻辑
你真的会玩SQL吗?系列目录 你真的会玩SQL吗?之逻辑查询处理阶段 你真的会玩SQL吗?和平大使 内连接.外连接 你真的会玩SQL吗?三范式.数据完整性 你真的会玩SQL吗?查询指定节点及其所有父节 ...
- 11 个很少人知道但很有用的 Linux 命令
Linux命令行吸引了大多数Linux爱好者.一个正常的Linux用户一般掌握大约50-60个命令来处理每日的任务.Linux命令和它们的转换对于Linux用户.Shell脚本程序员和管理员来说是最有 ...
随机推荐
- HYSBZ 1734 二分
传送门 题面: 农夫 John 建造了一座很长的畜栏,它包括N (2 <= N <= 100,000)个隔间,这些小隔间依次编号为x1,...,xN (0 <= xi <= 1 ...
- python爬虫加定时任务,制作微信提醒备忘录
一.任务的记录与提取 1.1 制作每日任务 为了便于爬取,推荐使用网页版的在线记事本,现在这种工具很多,我选择"石墨文档"进行操作演示.记录内容的 格式可以根据自己的需求和爬虫自行 ...
- 1.认识numpy的数组
认识ndarray --ndarray:用于存储单一数据类型的多维数组 ndarray基础属性的认识 import numpy as np #将numpy简写为np方便调用 1.通过以下实例来认识ar ...
- 开发C语言的3款神器,VS2019、VScode和IntelliJ Clion
一.Visual Studio 2019环境安装配置+代码调试 环境安装配置 首先我们要在Visual Studio官方网站去下载安装包 进入官网后会发现有三种版本可供下载,分别是社区版.专业版和企业 ...
- find文本处理(locate)实例学习记录
find文本处理(locate)实例学习记录 (一)按文件名称查找 按照文件名称查找是 find 最常见的用法,需要注意的是,搜索的文件名必须完全匹配,才能找到对应的文件. 1. 查找当前目录下所有 ...
- Go语言中使用K8s API及一些常用API整理
Go Client 在进入代码之前,理解k8s的go client项目是对我们又帮助的.它是k8s client中最古老的一个,因此具有很多特性. Client-go 没有使用Swagger生成器,就 ...
- 【linux】驱动-3-字符设备驱动
目录 前言 3. 字符设备驱动 3.1 Linux设备分类 3.2 设备相关概念 3.2.1 设备号 3.2.2 设备节点 3.2.3 APP open 文件理解 ** 3.3 数据结构 3.3.1 ...
- Git 在解决冲突的时候文件覆盖
更新代码导致被还原或覆盖的场景:1.触发冲突的必要条件是修改同一个文件且修改的位置非常近,否则Git会自动合并其内容避免更新代码导致被还原或覆盖的解决方案1.少修改的地方(生产环境.公网测试环境):推 ...
- 封装一个处理 react 异常的最简 ErrorBoundary 组件 🎅
前言 从 React 16 开始,引入了 Error Boundaries 概念,它可以捕获它的子组件中产生的错误,记录错误日志,并展示降级内容,具体 官网地址. 错误边界避免一个组件错误导致整个页面 ...
- Java第三章基础学习课后题练习
小结:final 类型 变量名 = 数值 定义常量使用 变量的原则*** 一定要"先声明,后使用",变量使用前必须先声明.这点就没php好玩:两种键盘输入方式InputStream ...