【Redis应用】查看附近(五)

news2025/2/5 6:09:05

🚗Redis应用学习·第五站~
🚩本文已收录至专栏:Redis技术学习

查看附近的XXX在我们的实际应用中非常广泛,能支持该功能的技术有很多,而在我们的Redis中主要依靠GEO数据结构来实现该功能!

一.GEO用法引入

GEO,全称Geolocation,代表地理坐标。可以在其中存储地理坐标信息,帮助我们根据经纬度来检索数据。常见的命令有:

  • GEOADD:添加一个或多个地理空间信息,包含:经度(longitude)、纬度(latitude)、值(member)
  • GEODIST:计算指定的两个点之间的距离并返回
  • GEOHASH:将指定member的坐标转为hash字符串形式并返回
  • GEOPOS:返回指定member的坐标
  • GEORADIUS:指定圆心、半径,找到该圆内包含的所有member,并按照与圆心之间的距离排序后返回。(6.2以后已废弃)
  • GEOSEARCH:在指定范围内搜索member,并按照与指定点之间的距离排序后返回。范围可以是圆形或矩形。(6.2以后新命令)
  • GEOSEARCHSTORE:与GEOSEARCH功能一致,不过可以把结果存储到一个指定的key。(6.2以后新命令)

我们可以在redis服务器使用命令 help xxx 查看指令的具体用法~

  • 使用示例:
# 1. 添加下面几条数据:
# 北京南站( 116.378248 39.865275 ) 北京站( 116.42803 39.903738 )
# 北京西站( 116.322287 39.893729 )
geoadd station 116.378248 39.865275 bjn 116.42803 39.903738 bj  116.322287 39.893729 bjx

在这里插入图片描述

# 1.计算北京西站到北京站的距离
geodist station bjx bj m

在这里插入图片描述

# 2.搜索天安门( 116.397904 39.909005 )附近10km内的所有火车站,并按照距离升序排序
georadius station 116.397904 39.909005 10 km asc

在这里插入图片描述

接下来我们结合附近商户实际场景来使用一番~

二.项目实战

(1) 案例说明

具体场景说明:
在这里插入图片描述

当我们点击美食之后,会出现一系列的商家,商家中可以按照多种排序方式,我们此时关注的是距离,这个地方就需要使用到我们的GEO,向后台传入当前app收集的地址(我们此处是写死的) ,以当前坐标作为圆心,同时绑定相同的店家类型type,以及分页信息,把这几个条件传入后台,后台查询出对应的数据再返回。

在这里插入图片描述

(2) 数据导入

要完成这个功能,我们首先要做的事情是将数据库表中的分类id:商户id:地理坐标信息导入到redis中去,我们可以按照商户类型做分组,类型相同的商户作为同一组,以typeId为key存入同一个GEO集合中即可
在这里插入图片描述

  • 导入数据实现代码:
@Test
void loadShopData() {
    // 1.查询所有店铺信息
    List<Shop> list = shopService.list();
    // 2.把店铺分组,按照typeId分组,typeId一致的放到一个集合
    Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
    // 3.分批完成写入Redis
    for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
        // 3.1.获取类型id
        Long typeId = entry.getKey();
        String key = "shop:geo:" + typeId;
        // 3.2.获取同类型的店铺的集合
        List<Shop> value = entry.getValue();
        List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(value.size());
        // 3.3.写入redis GEOADD key 经度 纬度 member
        for (Shop shop : value) {
            // 设置店铺id,x,y坐标
            //  GEOADD key longitude latitude member [longitude latitude member ...]
            
            // 3.3.1 一条一条添加写入,效率较低
            // stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString());
            
            // 3.3.2 批量添加再一次性写入
            locations.add(new RedisGeoCommands.GeoLocation<>(
                    shop.getId().toString(),
                    new Point(shop.getX(), shop.getY())
            ));
        }
        stringRedisTemplate.opsForGeo().add(key, locations);
    }
}
  • 如此我们便完成了数据的导入操作~
    在这里插入图片描述

(3) 功能实现

注意:SpringDataRedis的2.3.9版本并不支持Redis 6.2提供的GEOSEARCH命令,因此我们要提高版本或者使用6.2以后废弃的GEORADIUS命令,下面我们使用GEOSEARCH演示功能~

  • 第一步:导入pom
<!--去除starter内SpringDataRedis重新引入高版本或者直接提高starter版本-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-data-redis</artifactId>
            <groupId>org.springframework.data</groupId>
        </exclusion>
        <exclusion>
            <artifactId>lettuce-core</artifactId>
            <groupId>io.lettuce</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!--重新引入高版本-->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.6.2</version>
</dependency>
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.1.6.RELEASE</version>
</dependency>
  • 第二步:ShopController接收参数
// 接收 类别id,页码,以及当前坐标
@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);
}
  • 第三步:ShopServiceImpl逻辑处理
@Override
    public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
        // 1.判断是否需要根据坐标查询
        if (x == null || y == null) {
            // 不需要坐标查询,按数据库查询
            Page<Shop> page = query()
                    .eq("type_id", typeId)
                    .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
            // 返回数据
            return Result.ok(page.getRecords());
        }

        // 2.计算分页参数
        int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
        int end = current * SystemConstants.DEFAULT_PAGE_SIZE;

        // 3.查询redis、按照距离排序、分页。结果:shopId、distance
        String key = "shop:geo:" + typeId;
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo() 
            // 原始命令: GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE
                .search(
                        key,
                        GeoReference.fromCoordinate(x, y),
                        new Distance(5000),
 RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
                );
        // 4.解析出id
        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());
        }
        // 4.1.由于geosearch始终是0~end,因此需要分页效果需要直接手动截取
        // 手动截取 from ~ end的部分
        List<Long> ids = new ArrayList<>(list.size());
        Map<String, Distance> distanceMap = new HashMap<>(list.size());
        list.stream().skip(from).forEach(result -> {
            // 4.2.获取店铺id
            String shopIdStr = result.getContent().getName();
            ids.add(Long.valueOf(shopIdStr));
            // 4.3.获取距离
            Distance distance = result.getDistance();
            distanceMap.put(shopIdStr, distance);
        });
        // 5.根据id查询Shop
        String idStr = StrUtil.join(",", ids);
        List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
        for (Shop shop : shops) {
            shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
        }
        // 6.返回
        return Result.ok(shops);
    }
  • 如此我们便完成了附近店铺功能
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/761661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

问题解决:win10连接手机热点总是频繁自动断开

问题描述:尝试解决 问题解决:win10连接手机热点总是频繁自动断开 问题描述: 在使用win10笔记本电脑连接手机热点上网时,是不是的网络自动就断掉了,而且重新连上后,用着用着又断了, 这就让人有点恼火了, 尝试解决 重启电脑与手机 以前没出现过而现在有这种情况,可能是电脑或手机…

Spark复习笔记

文章目录 Spark在Hadoop高可用模式下读写HDFS运行流程构成组件作业参数RDD机制的理解算子map与mapPartition区别Repartition和Coalesce区别reduceBykey 与 groupByKeyreduceByKey、foldByKey、aggregateByKey、combineByKey区别cogroup rdd 实现原理宽窄依赖为什么要划分stage如…

Elasticsearch:语义搜索、知识图和向量数据库概述

结合对你自己的私有数据执行语义搜索的概述 什么是语义搜索&#xff1f; 语义搜索是一种使用自然语言处理算法来理解单词和短语的含义和上下文以提供更准确的搜索结果的搜索技术。 这种方法基于这样的想法&#xff1a;搜索引擎不仅应该匹配查询中的关键字&#xff0c;还应该尝…

LINUX命令:update-alternatives(软件版本管理工具)

前言   在基于 LINUX 操作系统之上安装所需开发环境组件时&#xff0c;可能会遇到无可避免的场景是&#xff1a;同一个组件&#xff0c;我们需要同时使用到两个或者更多的版本&#xff0c;比如 Java 有 1.6、1.7、1.8 等多版本&#xff0c;又比如 Python 有 2、3 等等&#x…

「2024」预备研究生mem-数学强化-整数、因数与倍数

一、整数、因数与倍数 二、带余除数 三、奇数与偶数 四、不定方程

Nature子刊-柔性薄膜上3D电极的直接激光写入

美国俄勒冈大学研究员设计了一种集成在柔性薄膜上的3D微电极阵列&#xff0c;其制造过程结合了传统的硅薄膜处理技术和双光子光刻在微米分辨率下的3D结构的直接激光书写技术&#xff0c;首次提出了一种产生高深宽比结构的方法。 发表在《自然通讯》杂志上的这项研究&#xff0c…

js实现控制台格式化打印版权信息(2023.7.16)

js代码在控制台格式化打印版权信息 2023.7.16 1、需求分析2、js实例&#xff08;浏览器版权信息&#xff09;2.1 百度一下2.1.1 js代码2.1.2 浏览器控制台输出效果 2.2 京东官网2.2.1 js代码2.2.2 浏览器控制台输出效果 2.3 EarthSDK地球页面2.3.1 js代码2.3.2 浏览器控制台输出…

【JMeter】JMeter进行JDBC数据库负载测试

JMeter进行JDBC数据库负载测试 前置准备1.创建线程组2.JDBC连接配置3.新建JDBC链接4.查看汇总报告 前置准备 此示例使用 MySQL 数据库驱动程序。 要使用此驱动程序&#xff0c;必须将其包含.jar文件&#xff08;例如 mysql-connector-java-X.X.X-bin.jar&#xff09;复制到 JM…

精选估值,解决买车卖车难题

在现代社会&#xff0c;车辆已经成为了人们生活中不可或缺的一部分。车辆的买卖交易也成为了许多人的生活中不可避免的问题。而估值则是买卖交易的过程中非常重要的一个环节。估值的准确与否直接影响到最后交易的结果。因此&#xff0c;选择一种准确便捷的估值方式就显得尤为重…

JAVA集合详解:用法、实例及适用场景

引言&#xff1a; 在JAVA编程中&#xff0c;集合是一种非常重要且常用的数据结构。通过使用集合&#xff0c;我们可以高效地组织和操作不同类型的数据。本文将深入探讨JAVA中各种集合的用法及实例&#xff0c;并介绍适用场景&#xff0c;以帮助更好地理解和应用集合。 --------…

TTX1994-可调谐激光器控制系统

花了两周时间&#xff0c;利用下班时间&#xff0c;设计了一个ITLA可调谐激光器控制系统&#xff0c;从硬件到软件。下面这个图片整套硬件系统&#xff0c;软件硬件都自己设计&#xff0c;可以定制&#xff0c;做到单片机问题也不大。相当于一套光源了 这是软件使用的界面&…

【算法】换根DP

文章目录 什么是换根DP例题分析——P3478 [POI2008] STA-Station题目列表1834. 树中距离之和⭐⭐⭐⭐⭐&#xff08;两次 dfs&#xff09;思路——冷静分析&#xff0c;列出式子算法分析⭐⭐⭐⭐⭐ 310. 最小高度树⭐⭐⭐⭐⭐2581. 统计可能的树根数目⭐⭐⭐⭐⭐C. Bear and Tr…

Coggle 30 Days of ML(23年7月)打卡

前言 最近开始关注LLM相关知识&#xff0c;但之前的NLP范式的技能不能丢。 这个练习还是比较适合我&#xff0c;感谢举办方选题&#xff0c;快速全部打卡一波。 打卡记录 任务一: 报名比赛&#xff0c;下载比赛数据集并完成读取 比赛链接&#xff1a;https://challenge.xfy…

第十六章:Understanding Convolution for Semantic Segmentation——理解用于语义分割的卷积

0.摘要 最近深度学习特别是深度卷积神经网络&#xff08;CNN&#xff09;的进展&#xff0c;显著提高了之前语义分割系统的性能。在这里&#xff0c;我们展示了通过操作与卷积相关的操作来改进逐像素的语义分割&#xff0c;这些操作在理论和实践上都具有价值。首先&#xff0c;…

【Java动态代理】—— 每天一点小知识

&#x1f4a7; J a v a 动态代理 \color{#FF1493}{Java动态代理} Java动态代理&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法》专栏的文章图文并茂&am…

PyTorch 深度学习处理多维特征的输入

import numpy as np import torch import matplotlib.pyplot as plt# prepare dataset xy np.loadtxt(diabetes.csv, delimiter,, dtypenp.float32) x_data torch.from_numpy(xy[:, :-1]) # 第一个‘&#xff1a;’是指读取所有行&#xff0c;第二个‘&#xff1a;’是指从第…

Linux常用命令——eject命令

在线Linux命令查询工具 eject 用来退出抽取式设备 补充说明 eject命令用来退出抽取式设备。若设备已挂入&#xff0c;则eject命令会先将该设备卸除再退出。 eject允许可移动介质&#xff08;典型是cd-ROM、软盘、磁带、或者JAZ以及zip磁盘&#xff09;在软件控制下弹出。该…

Visual Studio 2022打包exe ,自动按日期生成文件

echo offREM 获取当前的日期和时间 set YEAR%DATE:~0,4% set MONTH%DATE:~5,2% set DAY%DATE:~8,2% set HOUR%TIME:~0,2% set MINUTE%TIME:~3,2% set SECOND%TIME:~6,2%REM 获取原始文件名 set "FilePath$(TargetPath)" for %%F in ("%FilePath%") do (set…

第46节:cesium 水面效果(含源码+视频)

结果示例: 完整源码: <template><div class="viewer"><vc-viewer @ready="ready" :logo="false"><!

【AT89C52单片机项目】99累减器

实验目的 掌握STC89C52RC单片机最小系统构成&#xff0c;最小系统由单片机芯片、时钟电路及复位电路组成。 掌握STC89C52RC单片机开发板与数码管的原理图、控制方式。 掌握对单片机I/O的复杂控制 熟练掌握C语言的设计和调试方法。 实验仪器 一套STC89C52RC开发板套件&…