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 的工作流程可以分为几个主要的步骤&…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
