Redis --- 使用GEO实现经纬度距离计算
什么是GEO?

Spring Boot 项目中可以通过 Spring Data Redis 来使用 Redis GEO 功能,主要通过 RedisTemplate 和 GeoOperations 接口来操作地理位置数据。
@Service
public class GeoService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 存储地理位置public void addGeoLocation(String key, String member, double longitude, double latitude) {GeoOperations<String, Object> geoOperations = redisTemplate.opsForGeo();geoOperations.add(key, new GeoLocation<>(member, longitude, latitude));}// 获取两个地理位置之间的距离public Double getDistance(String key, String member1, String member2) {GeoOperations<String, Object> geoOperations = redisTemplate.opsForGeo();return geoOperations.distance(key, member1, member2, GeoUnit.KILOMETERS).getValue();}// 查找指定经纬度范围内的地理位置public List<GeoLocation<Object>> getNearbyLocations(String key, double longitude, double latitude, double radius) {GeoOperations<String, Object> geoOperations = redisTemplate.opsForGeo();return geoOperations.radius(key, longitude, latitude, radius, GeoUnit.KILOMETERS);}
}
示例调用:
GeoService geoService = new GeoService();// 添加地理位置
geoService.addGeoLocation("cities", "Beijing", 116.4074, 39.9042);
geoService.addGeoLocation("cities", "Shanghai", 121.4737, 31.2304);// 计算距离
Double distance = geoService.getDistance("cities", "Beijing", "Shanghai");
System.out.println("Distance between Beijing and Shanghai: " + distance + " km");// 查找附近的地点
List<GeoLocation<Object>> nearbyCities = geoService.getNearbyLocations("cities", 116.4074, 39.9042, 100);
for (GeoLocation<Object> city : nearbyCities) {System.out.println(city.getName());
}
业务需求:
对附近的商户进行搜索
首先导入redis依赖:
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>2.7.18</version>
</dependency>
<dependency><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId><version>6.4.1.RELEASE</version>
</dependency>
随后按照商户类型做分组,类型相同的商户作为一组,以typeId为key存入同一个GEO集合中:

@SpringBootTest
public class HmDianPingApplicationTests {@Autowiredprivate IShopService shopService;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Testpublic void loadShopData(){// 查询店铺信息List<Shop> list = shopService.list();// 把店铺按照typeId分组,typeId一致的放入同一集合中Map<Long,List<Shop>> map = list.stream().collect(Collectors.groupingBy(shop -> shop.getTypeId()));for(Map.Entry<Long,List<Shop>> entry:map.entrySet()){Long typeId = entry.getKey();String key = "shop:geo" + typeId;List<Shop> value = entry.getValue();// GeoLocation的泛型是member的数据类型// GeoLocation的内部是member与point,point是经纬度List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>();// 写入Redis GEOADD key 经度 纬度 memberfor(Shop shop:value){// 效率低(有多少个店铺就需要存入多少次)//stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(),shop.getY()), shop.getId().toString());// 使用可迭代的集合直接存入locations.add(new RedisGeoCommands.GeoLocation<>(shop.getId().toString(),new Point(shop.getX(), shop.getY())));}stringRedisTemplate.opsForGeo().add(key, locations);}}
}

根据上面的接口文档,在shopController中写入接口:
@RestController
@RequestMapping("/shop")
public class ShopController {@Resourcepublic IShopService shopService;/*** 根据商铺类型分页查询商铺信息* @param typeId 商铺类型* @param current 页码* @return 商铺列表*/@GetMapping("/of/type")public Result queryShopByType(@RequestParam("typeId") Integer typeId,@RequestParam(value = "current", defaultValue = "1") Integer current,@RequestParam(value = "x",required = false) Double x, // 可为空@RequestParam(value = "y",required = false) Double y // 可为空) {return shopService.queryShopByType(typeId,current,x,y);}
}
下面通过 Redis 和数据库的结合实现了商店查询功能。使用 Redis 的地理位置功能来加速根据坐标查询商店的速度并支持分页。首先检查是否提供了坐标,如果没有坐标就直接从数据库查询,如果有坐标则通过 Redis 查询地理位置数据并计算距离,最终返回分页后的商店信息。
@Service // 声明为 Spring 服务类,允许 Spring 自动管理该类的生命周期
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {// 引入 Redis 的 StringRedisTemplate,用于执行 Redis 操作@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {// 如果没有提供坐标 x 和 y,则不需要通过 Redis 进行地理查询,直接通过数据库查询商店if(x == null || y == null){// 构建数据库查询条件:根据商店类型查询,并分页Page<Shop> page = query().eq("type_id", typeId) // 根据商店类型 ID 进行筛选.page(new Page<>(current, 1000)); // 分页查询,每页返回 1000 条记录return Result.ok(page.getRecords()); // 返回查询结果}// 如果提供了坐标,计算分页参数int from = (current - 1) * 1000; // 计算当前页起始记录int end = current * 1000; // 计算当前页结束记录// 查询 Redis,按照距离排序进行分页,结果包括 shopId 和距离String key = "shop:geo:" + typeId; // Redis 中存储商店位置的 key,格式为 shop:geo:typeIdGeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(key, // Redis 中的 Geo 数据的 keyGeoReference.fromCoordinate(x, y), // 从提供的坐标 (x, y) 开始查询new Distance(5000), // 设置搜索半径为 5000 米RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end) // 包括距离信息,并限制查询结果数量);// 如果 Redis 查询结果为空,则直接返回空列表if(results == null) {return Result.ok(Collections.emptyList());}// 获取查询结果的内容List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();// 如果查询结果中没有足够的记录显示当前页的内容,返回空列表if(list.size() <= from){return Result.ok(Collections.emptyList());}// 使用 stream 流式处理查询结果,跳过前 from 条记录,获取分页结果List<Long> ids = new ArrayList<>(list.size()); // 存储查询结果中商店的 IDMap<String, Distance> distanceMap = new HashMap<>(list.size()); // 存储商店 ID 与其对应的距离list.stream().skip(from).forEach(result -> {// 获取店铺的 ID,GeoLocation.getName() 方法返回的是商店 IDString shopIdStr = result.getContent().getName();ids.add(Long.parseLong(shopIdStr)); // 将商店 ID 转换为 Long 类型并存入 ids 列表// 获取商店距离Distance distance = result.getDistance();distanceMap.put(shopIdStr, distance); // 将商店 ID 和距离存入 distanceMap});// 构建 SQL 查询,查询所有符合条件的商店String idStr = StrUtil.join(",", ids); // 将 ids 列表转化为逗号分隔的字符串List<Shop> shops = query().in("id", ids) // 查询商店 ID 在 ids 列表中的所有商店.last("order by ids " + idStr + ")") // 通过 SQL 排序,根据商店 ID 排序.list(); // 执行查询并返回商店列表// 遍历商店信息,将每个商店的距离装入到 Shop 对象中for (Shop shop : shops) {shop.setDistance(distanceMap.get(shop.getId().toString()).getValue()); // 设置商店的距离}// 返回查询结果return Result.ok(shops); // 返回商店信息}
}
通过将 Redis 的地理位置数据与数据库中的商店信息结合,优化了查询的效率,尤其适合商店类型和位置相关的查询场景。
相关文章:
Redis --- 使用GEO实现经纬度距离计算
什么是GEO? Spring Boot 项目中可以通过 Spring Data Redis 来使用 Redis GEO 功能,主要通过 RedisTemplate 和 GeoOperations 接口来操作地理位置数据。 Service public class GeoService {Autowiredprivate RedisTemplate<String, Object> red…...
【0403】Postgres内核 检查(procArray )给定 db 是否有其他 backend process 正在运行
文章目录 1. 给定 db 是否有其他 backend 正在运行1.1 获取 allPgXact[] 索引1.1.1 MyProc 中 databaseId 初始化实现1.2 allProcs[] 中各 databaseId 判断1. 给定 db 是否有其他 backend 正在运行 CREATE DATABASE 语句创建用户指定 数据库名(database-name)时候, 会通过 …...
[数据结构] Set的使用与注意事项
目录 Set的说明 常见方法说明 注意事项 TreeSet使用案例 Set的说明 Set与Map主要的不同有两点: Set是继承自Collection的接口类,Set中只存储了Key. 常见方法说明 方法解释boolean add(E e)添加元素,但重复元素不会被添加成功void clear()清空集合boolean contains(Object…...
amis组件crud使用踩坑
crud注意 过滤条件参数同步地址栏 默认 CRUD 会将过滤条件参数同步至浏览器地址栏中,比如搜索条件、当前页数,这也做的目的是刷新页面的时候还能进入之前的分页。 但也会导致地址栏中的参数数据合并到顶层的数据链中,例如:自动…...
离线统信系统的python第三方库批量安装流程
一、关于UOS本机 操作系统:UOS(基于Debian的Linux发行版) CPU:海光x86 二、具体步骤 1、在联网的电脑上用控制台的pip命令批量下载指定版本的第三方库 方法A cd <目标位置的绝对路径> pip download -d . --platform many…...
韶音科技:消费电子行业售后服务实现数字化转型,重塑客户服务体系
韶音科技:消费电子行业售后服务实现数字化转型,重塑客户服务体系 在当今这个科技日新月异的时代,企业之间的竞争早已超越了单纯的产品质量比拼,**售后服务成为了衡量消费电子行业各品牌实力与客户满意度的关键一环。**深圳市韶音…...
神经网络|(九)概率论基础知识-泊松分布及python仿真
【1】引言 在前序学习进程中,我们已经知晓二项分布是多重伯努利分布,二伯努利分布对应的是可以无限重复、结果只有两种可能的随机试验。 相关文章链接为: 神经网络|(八)概率论基础知识-二项分布及python仿真-CSDN博客 上述文章还调用nump…...
114,【6】攻防世界 web wzsc_文件上传
进入靶场 传个桌面有的 直接空白了 我们 访问一下上传的东西 /index 没显示用于解析的.htaccess和.user.ini 文件,还两个都不显示 .htaccess 和 .user.ini 文件分别用于 Apache 服务器和 PHP-FPM 环境的目录级配置 但上传的时候bp查看状态码是200,…...
【Kubernetes的SpringCloud最佳实践】有Service是否还需要Eureka?
在 Kubernetes 中部署 Spring Cloud 微服务时,是否还需要 Eureka 取决于具体场景和架构设计。以下是详细的实践建议和结论: 1. Kubernetes 原生服务发现 vs Eureka Kubernetes 自身提供了完善的服务发现机制(通过 Service 资源)&…...
SQL最佳实践(笔记)
写在前面: 之前baeldung的Java Weekly Reviews里面推荐了一篇关于SQL优化的文章,正好最近在学习数据库相关知识,记一些学习笔记 原文地址:SQL Best Practices Every Java Engineer Must Know 1. 使用索引 使用索引…...
vue3学习四
七 标签ref属性 设置标签ref属性,类似于设置标签id。 普通标签 <template name"test4"> <p ref"title" id"title" click"showinfo">VIEW4</p> <View3/><script lang"ts" setup>…...
C# LiteDB 使用教程
一、引言 在软件开发中,数据存储和管理是至关重要的一环。对于小型项目或者对性能和便捷性有较高要求的场景,传统的大型数据库可能显得过于笨重。而 LiteDB 作为一款轻量级的嵌入式 NoSQL 数据库,为开发者提供了一个简洁、高效的解决方案。它…...
Python Pandas(3):DataFrame
1 介绍 DataFrame 是 Pandas 中的另一个核心数据结构,类似于一个二维的表格或数据库中的数据表。它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型值)。DataFrame 既有行索引也有列索引,它可以被看做由…...
使用通义灵码 ai编程 来提高开发效率
1、我们先新建一个Hello,world的vue3项目(快速上手 | Vue.js) 创建好以后,运行以下界面: about界面如下,现在我们让灵码给我们修改一下这个字体的颜色及加点其它的样式: 2、先选中样式…...
【OpenCV】入门教学
🏠大家好,我是Yui_💬 🍑如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀 🚀如有不懂,可以随时向我提问,我会全力讲解~ ὒ…...
大数据项目4:基于spark的智慧交通项目设计与实现
项目概述 项目直达 www.baiyuntu.com 随着交通数据的快速增长,传统的交通管理方式已无法满足现代城市的需求。交通大数据分析系统通过整合各类交通数据,利用大数据技术解决交通瓶颈问题,提升交通管理效率。本项目旨在通过大数据技术&#…...
netcore openTelemetry+prometheus+grafana
一、netcore项目 二、openTelemetry 三、prometheus 四、grafana添加Dashborad aspire/src/Grafana/dashboards at main dotnet/aspire GitHub 导入:aspnetcore.json和aspnetcore-endpoint.json 效果:...
Spring Boot接入Deep Seek的API
1,首先进入deepseek的官网:DeepSeek | 深度求索,单击右上角的API开放平台。 2,单击API keys,创建一个API,创建完成务必复制!!不然关掉之后会看不看api key!!&…...
Git、Github和Gitee完整讲解:丛基础到进阶功能
第一部分:Git 是什么? 比喻:Git就像是一本“时光机日记本” 每一段代码的改动,Git都会帮你记录下来,像是在写日记。如果出现问题或者想查看之前的版本,Git可以带你“穿越回过去”,找到任意时间…...
MyBatis的工作流程是怎样的?
大家好,我是锋哥。今天分享关于【MyBatis的工作流程是怎样的?】面试题。希望对大家有帮助; MyBatis的工作流程是怎样的? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MyBatis 的工作流程可以分为几个主要的步骤&…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
