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 的工作流程可以分为几个主要的步骤&…...

Maven 安装配置(完整教程)
文章目录 一、Maven 简介二、下载 Maven三、配置 Maven3.1 配置环境变量3.2 Maven 配置3.3 IDEA 配置 四、结语 一、Maven 简介 Maven 是一个基于项目对象模型(POM)的项目管理和自动化构建工具。它主要服务于 Java 平台,但也支持其他编程语言…...

分享如何通过Mq、Redis、XxlJob实现算法任务的异步解耦调度
一、背景 1.1 产品简介 基于大模型塔斯,整合传统的多项能力(NLP、OCR、CV等),构建以场景为中心的新型智能文档平台。通过文档审阅,实现结构化、半结构化和非结构化文档的信息获取、处理及审核,同时基于大…...

发布:大彩科技DN系列2.8寸高性价比串口屏发布!
一、产品介绍 该产品是一款2.8寸的工业组态串口屏,采用2.8寸液晶屏,分辨率为240*320,支持电阻触摸、电容触摸、无触摸。可播放动画,带蜂鸣器,默认为RS232通讯电平,用户短接屏幕PCB上J5短接点即可切换为TTL电…...

集合类不安全问题
ArrayList不是线程安全类,在多线程同时写的情况下,会抛出java.util.ConcurrentModificationException异常 解决办法: 1.使用Vector(ArrayList所有方法加synchronized,太重) 2.使用Collections.synchronized…...

【基于SprintBoot+Mybatis+Mysql】电脑商城项目之上传头像和新增收货地址
🧸安清h:个人主页 🎥个人专栏:【Spring篇】【计算机网络】【Mybatis篇】 🚦作者简介:一个有趣爱睡觉的intp,期待和更多人分享自己所学知识的真诚大学生。 目录 🚀1.上传头像 -持久…...

AI知识库和全文检索的区别
1、AI知识库的作用 AI知识库是基于人工智能技术构建的智能系统,能够理解、推理和生成信息。它的核心作用包括: 1.1 语义理解 自然语言处理(NLP):AI知识库能够理解用户查询的语义,而不仅仅是关键词匹配。 …...

20240817 联想 笔试
文章目录 1、选择题1.11.21.31.41.51.61.71.81.91.101.111.121.131.141.151.161.171.181.191.202、编程题2.12.2岗位:Linux开发工程师 题型:20 道选择题,2 道编程题 1、选择题 1.1 有如下程序,程序运行的结果为 (D) #include <stdio.h>int main() {int k = 3...

IntelliJ IDEA 安装与使用完全教程:从入门到精通
一、引言 在当今竞争激烈的软件开发领域,拥有一款强大且高效的集成开发环境(IDE)是开发者的致胜法宝。IntelliJ IDEA 作为 JetBrains 公司精心打造的一款明星 IDE,凭借其丰富多样的功能、智能精准的代码提示以及高效便捷的开发工…...

【JVM详解一】类加载过程与内存区域划分
一、简介 1.1 概述 JVM是Java Virtual Machine(Java虚拟机)的缩写,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相关…...

250207-MacOS修改Ollama模型下载及运行的路径
在 macOS 上,Ollama 默认将模型存储在 ~/.ollama/models 目录。如果您希望更改模型的存储路径,可以通过设置环境变量 OLLAMA_MODELS 来实现。具体步骤如下: 选择新的模型存储目录:首先,确定您希望存储模型的目标目录路…...