【案例实战】SpringBoot整合Redis的GEO实现查找附近门店功能

像我们平常美团点外卖的时候,都会看到一个商家距离我们多少米。还有类似QQ附近的人,我们能看到附近的人距离我们有多少米。

在这里插入图片描述

那么这些业务是怎么做的呢?是如何实现 基于位置的附近服务系统呢。

在去了解基于位置的附近服务之前,我们先来看一下什么是GIS技术。

GIS代表地理信息系统,是一种用于收集、存储、分析、管理和显示地理空间数据的技术。GIS利用计算机软件和硬件来创建、管理、分析和可视化地理信息,使用户能够更好地了解和解决地理空间问题。

简言之地图上的每一个位置都会一个经纬度坐标。根据这个坐标我们查出来附近的人,或者附近的门店之类的。

下面是基于百度的地图经纬度定位系统,大家可以自己体验下,你给它一个经纬度,他能给你定位到地图的某一个点。也就是当前经纬度的位置。

网址:http://jingweidu.757dy.com/

在这里插入图片描述

我们既然了解了经纬度这个概念,那么Redis 3.2版本之后新增的一个数据类型,是一种用于处理地理位置信息的数据结构。

GEO(地理位置):存储和查询地理位置数据,并快速地计算距离和位置集合的交集。

应用场景

  • 位置服务:可以使用GEO结构存储用户或商家的位置信息,并计算用户或商家之间的距离。
  • 商业分析:可以使用GEO结构在地图上可视化商家的分布状况,以进行市场分析和营销策略的制定。
  • 推荐系统:可以使用GEO结构计算用户与商家之间的距离,以实现根据附近商家推荐和推荐商家位置排名等功能。或者用于实现拼团和卡券等代表性做法,诸如可以实现商家对距离自己较近的用户进行自动优惠券发放等。

Redis的GEO数据结构常见的命令

  • geoadd:增加某个地理位置的坐标
    • 语法 GEOADD key longitude latitude member
  • geopos:获取某个地理位置的坐标
    • 语法 GEOPOS key [member [member ...]]
  • geodist:获取两个地理位置的距离
    • 语法 GEODIST key member1 member2 [M | KM | FT | MI]
    • 范围单位:m | km | ft | mi --> 米 | 千米 | 英尺 | 英里
  • georadius:根据给定地理位置坐标获取指定范围内的地理位置集合
    • 语法 GEORADIUS key longitude latitude radius <M | KM | FT | MI>
  • georadiusbymember:根据给定地理位置获取指定范围内的地理位置集合。
    • 语法 GEORADIUSBYMEMBER key member radius <M | KM | FT | MI>
  • geohash:获取某个地理位置的geohash值
    • 语法 GEOHASH key [member [member ...]]

SpringBoot整合案例实战

需求背景:一个用户想要找他的附近美食,使用Redis的GEO结构。

SpringBoot项目整合RedisTemplate这里就不过多介绍啦。

<!-- 创建SpringBoot项目加入redis的starter依赖 -->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

首先我们要先准备一些商家信息,我们先定义一个商家的实体类。

/**
 * 定义商家店铺实体类
 * @author lixiang
 * @date 2023/6/21 09:31
 */
@Data
public class Shop {

    /**
     * id
     */
    private String id;

    /**
     * 名称
     */
    private String name;

    /**
     * 精度
     */
    private BigDecimal accuracy;

    /**
     * 纬度
     */
    private BigDecimal latitude;

    /**
     * 店铺星级
     */
    private String star;

    /**
     * 评分
     */
    private BigDecimal score;

}

定义商店店铺测试数据的文本文件,通过读文件的方式放到内存中。

0f8207fd52344b348584f82d7ffef389 淮南牛肉汤 15.361239 20.115126 五星 9.7
c0304660e5be494eaff45ce26fcb9bf9 华莱士.鸡肉汉堡 13.361239 21.115126 四星 8.9
a998f4386fa34e16ba9ccf3f448bab7b 驴肉火烧 18.361239 24.115126 五星 9.5
76e50c6b464740bc888a226687961d0a 谷香煎饼 29.361239 24.115126 五星 9.0
1e84ace9b8c6492db416d6abd982e60d 老王鲜肉饼.砂锅 52.361239 40.115126 五星 9.0
3c9557c45a9f4e51ac3bd3ac39052622 麦多馅饼 51.361239 42.115126 三星 7.8
4a5771f48a4f4c61ba1c0b992989af86 张亮麻辣烫 78.361239 67.115126 五星 9.4
6c1b322c2f2546f4a286f745b3b800c3 农家大烤盘饭 80.361239 -67.115126 五星 9.0
2d577e6196414148a7809a469ded51c0 沙野轻食 -80.361239 -67.115126 三星 7.6
588fa28618b147fa87a904a541e7833b 卷饼王.炸串 70.361239 67.115126 五星 9.6
8247ba41fc2942f5b29e91cd42a7b422 凉皮先生.肉夹馍 29.361239 80.115126 五星 9.6
00de5559ecfc4c419b4e6adef8bffee6 火炉火韩式拌饭 12.361239 10.115126 五星 9.9
29d18fc219ed4ad09bdaf2fe806f796f 南城.黄焖鸡米饭 72.361239 50.115126 三星 7.9
770c8f0bbbb44f259d58d1e5b350fbd4 李大姐水饺 52.361239 42.115126 三星 7.9
b3d5dd8773e6475b9bb16ad25f876afb 田老师烤肉 52.469669 42.225196 三星 7.4
d112f7be99c24142b422633cdf15461b 老家炒饼 52.362239 42.145126 四星 8.4
8d59cae232da485d9cb77f6c6060c929 地摊烤冷面 52.398239 42.416526 四星 8.7
5ade01f108ba4ba884a8ec1d37bdf9bb 卤汁拌饭 83.361239 68.115126 四星 8.0
96f462d9a20f40419f18a0f4936ad099 人民公社大饭菜 20.361239 10.115126 四星 8.8
6b0b5955c6b8444ca49a5a2ba39ab49b 炸串王铁板烧 34.361239 20.115126 五星 9.8

我们定义两个方法,一个是获取列表,一个是根据ID获取商户信息。

/**
 * @author lixiang
 * @date 2023/6/21 09:53
 */
public class ShopData {

    public final static String SHOP_KEY = "shop:location";

    private final static List<Shop> SHOP_LIST;

    static {
        SHOP_LIST = new ArrayList<>();
        BufferedReader reader;
        try {
            reader = new BufferedReader(new FileReader("/Users/mac/IdeaProjects/spring-redis-demo/src/main/resources/shop.txt"));
            String line;
            do{
                line = reader.readLine();
                if (!StringUtils.isEmpty(line)){
                    String[] split = line.split(" ");
                    Shop shop = new Shop();
                    shop.setId(split[0]);
                    shop.setName(split[1]);
                    shop.setAccuracy(new BigDecimal(split[2]));
                    shop.setLatitude(new BigDecimal(split[3]));
                    shop.setStar(split[4]);
                    shop.setScore(new BigDecimal(split[5]));
                    SHOP_LIST.add(shop);
                }
            }while (line != null);
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据列表
     * @return
     */
    public static List<Shop> getData(){
        return SHOP_LIST;
    }

    /**
     * 获取数据map结构,根据ID获取商家信息
     * @return
     */
    public static Map<String,Shop> getDataMap(){
        return SHOP_LIST.stream().collect(Collectors.toMap(Shop::getId,obj->obj));
    }
}

测试数据是否进入到集合中。

    public static void main(String[] args) {
        List<Shop> data = ShopData.getData();
        for (Shop datum : data) {
            System.out.println(datum);
        }
    }

在这里插入图片描述

ok,没有问题。接下来我们开始写一个接口用于将地理位置信息同步到Redis中。封装GEO的操作组件。

/**
 * @author lixiang
 * @date 2023/6/21 11:32
 */
@Component
public class GeoComponent {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 添加成员
     * @param key
     * @param lon 经度
     * @param lat 纬度
     * @param member 成员
     * @return
     */
    public Long geoAdd(String key, double lon, double lat, String member){
        return redisTemplate.opsForGeo().add(key, new Point(lon, lat), member);
    }

    /**
     * 获取两个成员的距离
     * @param key
     * @param member1
     * @param member2
     * @return
     */
    public Distance geoDist(String key, String member1, String member2){
        return redisTemplate.opsForGeo().distance(key, member1, member2);
    }

    /**
     * 获取两个成员的距离
     * @param key
     * @param member1
     * @param member2
     * @param metric 度规(枚举)(km、m)
     * @return
     */
    public Distance geoDist(String key, String member1, String member2, Metrics metric){
        return redisTemplate.opsForGeo().distance(key, member1, member2, metric);
    }

    /**
     * 获取成员经纬度
     * @param key
     * @param members
     * @return
     */
    public List<Point> geoPos(String key, String... members){
        return redisTemplate.opsForGeo().position(key, members);
    }

    /**
     * 获取某个成员附近(距离范围内)的成员
     * @param key
     * @param member 成员
     * @param v 距离
     * @param metric  度规(枚举)(km、m)
     * @return
     */
    public List<String> geoRadiusByMember(String key, String member, double v, Metrics metric){
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = redisTemplate.opsForGeo().radius(key, member, new Distance(v, metric));
        List<String> result = new ArrayList<>();
        for(GeoResult<RedisGeoCommands.GeoLocation<String>> geoResult :geoResults.getContent()){
            result.add(geoResult.getContent().getName());
        }
        return result;
    }

    /**
     * 获取某个成员附近(距离范围内)的成员
     * @param key
     * @param member 成员
     * @param v 距离
     * @param metric  度规(枚举)(km、m)
     * @param args
     * 示例:RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().includeDistance().limit(1).sortAscending();
     * includeCoordinates:结果包含坐标,includeDistance:结果包含距离,limit:返回数量:sort...:排序
     * @return GeoResults
     * geoResult.getContent().getName() 元素名称
     * geoResult.getContent().getPoint() 元素坐标
     * geoResult.getDistance() 元素距离
     */
    public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusByMember(String key, String member, double v, Metrics metric, RedisGeoCommands.GeoRadiusCommandArgs args){
        return redisTemplate.opsForGeo().radius(key, member, new Distance(v, metric), args);
    }
}

定义ShopService-syncShopLocationData()方法

		@Override
    public void syncShopLocationData() {
        List<Shop> data = ShopData.getData();
        //地理位置信息同步到Redis
        data.forEach(obj->{
            double accuracy = obj.getAccuracy().doubleValue();
            double latitude = obj.getLatitude().doubleValue();
            String id = obj.getId();
            geoComponent.geoAdd(SHOP_KEY,accuracy,latitude,id);
        });
    }
    @Autowired
    private ShopService shopService;
    private final static String SHOP_KEY = "shop:location";
    @GetMapping("/syncShopLocationToRedis")
    public void syncShopLocationToRedis(){
        shopService.syncShopLocationData();
    }

测试调用,数据已经写到Redis中。

在这里插入图片描述

ok,数据已经存储进来。接下来我们来实现一下按照用户的距离,搜索用户附近10km以内的商家,并按照着距离从小到大的进行排列商家的列表。

这里我们先在Service中定义一个根据ID查找商户的方法。有了我们之前的ShopData提供的方法,这里编写就很简单。

		@Override
    public Shop getShopById(String id) {
        return ShopData.getDataMap().get(id);
    }

定义controller方法,这里模拟里一个用户信息,给他一个经纬度,去查找附近的门店。

    @GetMapping("/getShopListByLocation")
    public List<ShopVO> getShopListByLocation(){
        //模拟用户信息
        Map<String,Object> user = new HashMap<>();
        user.put("accuracy",70.361239);
        user.put("latitude",67.115126);
        user.put("name","李祥");
        user.put("id", UUID.randomUUID().toString().replace("-",""));
        List<ShopVO> shopVO = shopService.getShopListByLocation(user);
        return shopVO;
    }

定义service层里面的逻辑。

		@Override
    public List<ShopVO> getShopListByLocation(Map<String, Object> user) {

        List<ShopVO> shopVOS = new ArrayList<>();

        // 获取用户的坐标位置
        double accuracy = (double) user.get("accuracy");
        double latitude = (double) user.get("latitude");
        String userId = String.valueOf(user.get("id"));

        // 将用户位置加入到Redis
        geoComponent.geoAdd(ShopData.SHOP_KEY, accuracy, latitude, userId);
        // 获取用户附近的门店
        List<String> shopIds = geoComponent.geoRadiusByMember(ShopData.SHOP_KEY, userId, 10, Metrics.KILOMETERS);
        for (String shopId : shopIds) {
            //如果是当前userId则直接跳出
            if (shopId.equals(userId)) {
                continue;
            }
            //获取shop信息
            Shop shop = this.getShopById(shopId);
            ShopVO shopVO = new ShopVO();
            BeanUtils.copyProperties(shop, shopVO);
            //获取两点的距离
            double distance = geoComponent.geoDist(ShopData.SHOP_KEY, userId, shopId, Metrics.KILOMETERS).getValue();
            //保留一位小数
            distance = new BigDecimal(distance).setScale(1, BigDecimal.ROUND_DOWN).doubleValue();
            shopVO.setDistance(distance);
            shopVOS.add(shopVO);
        }
        // 删除Redis中用户位置。
        geoComponent.geoDelete(ShopData.SHOP_KEY,userId);
        //排序 返回
        return shopVOS.stream().sorted(Comparator.comparingDouble(ShopVO::getDistance)).collect(Collectors.toList());    
    }

测试验证:

在这里插入图片描述

OK,那么关于Redis的GEO数据结构的实操,我们就讲到这里啦。觉得博主写的不错的记得给个赞哦!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

个人域名邮箱无法给gmail发邮件

问题描述 我注册了一个域名 mydomain.com, 并在此域名下创建了 mailbox&#xff0c;从该邮箱向外发送邮件和接收邮件会失败。 主要是一些配置工作没有做好。 接收邮件 当创建邮箱 xxxmydomain.com&#xff0c;尝试向该邮箱发送邮件时&#xff0c;邮件会被拒收&#xff0c;并…

在Linux中安装RabbitMQ

RabbitMQ下载网址 Socat下载网址 erlang下载网址 RabbitMQ安装包依赖于Erlang语言包的支持&#xff0c;所以需要先安装Erlang语言包&#xff0c;再安装RabbitMQ安装包 通过Xftp软件将这三个压缩包上传到linux中的opt目录下 ,双击即可 在安装之前先查询…

Css基础:盒子模型

1.盒子模型的构成&#xff1a; 边框 外边距 内边距 实际内容 2.table表格的单元格之间的线太粗需要border-collapse:collapse;合并一下边框宽度 3.内边距 padding 4.外边距 margin 块元素水平居中的做法&#xff0c;margin:0 auto; 行内元素和行内块元素 水平居中做…

【Jenkins】Jenkins构建后端流水线

目录 一、新建任务1、输入任务名称&#xff0c;选择构建项目类型&#xff08;这里我选择的是Maven项目&#xff09;&#xff0c;任务名称一般格式为&#xff1a;项目名称-前后端2、创建成功后的结果 二、配置流水线1、进入刚创建好的任务页面中&#xff0c;点击配置2、General配…

快速入门uniapp——从环境搭建到项目实践

&#x1f642;博主&#xff1a;小猫娃来啦 &#x1f642;文章核心&#xff1a;快速入门uniapp——从环境搭建到项目实践 文章目录 初步介绍UniApp开发环境搭建下载和安装UniApp开发工具创建新项目&#xff08;HBuilderX&#xff09;开发工具界面介绍 UniApp基础知识页面结构页面…

SQLite Expert Professional将ACCESS数据库文件导入到SQLITE

一、下载与安装 下载对应的位数的SQLite Expert&#xff1a;http://www.sqliteexpert.com/download.html &#xff0c;建议下载professional版本的&#xff0c;功能更加强大。 如果官网进不去可以到百度云下载&#xff1a;https://pan.baidu.com/s/17igndAqQ7SQ57LcjwS4WIQ …

原型模式:如何最快速地clone一个HashMap散列表?

我们还像学习建造者模式一样 思考 什么是原型模式&#xff1f;主要解决哪些问题&#xff1f; 如果对象的创建成本比较大&#xff0c;而同一个类的不同对象之间差别不大&#xff08;大部分字段都相同&#xff09;&#xff0c;在这种情况下&#xff0c;我们可以利用对已有对象…

关于PHP 使用 Elastic Search8的相关经历

你好&#xff01; 如果你也是第一次使用ES8和PHP对接使用&#xff0c;这里或许有一些心得可以为你解决一些问题。 本地环境所需工具 windows 版本搭建 Elastic Search 如下图&#xff0c;通过官网下载一个windows版本的Elastic Search 执行.bat文件即可启动 https://localhos…

ChatGPT 有什么新奇的使用方式?

先来看看ChatGPT对此问题如何作答 ChatGPT对此问题如何作答 ChatGPT是什么 ChatGPT是一种基于自然语言处理的语言模型&#xff0c;由OpenAI开发。它是建立在GPT&#xff08;Generative Pre-trained Transformer&#xff09;架构的基础上的&#xff0c;采用了深度学习技术。GP…

在树莓派上搭建web站点并发布互联网上线【无需公网IP】

文章目录 概述使用 Raspberry Pi Imager 安装 Raspberry Pi OS设置 Apache Web 服务器测试 web 站点安装静态样例站点将web站点发布到公网安装 Cpolar内网穿透cpolar进行token认证生成cpolar随机域名网址生成cpolar二级子域名将参数保存到cpolar配置文件中测试修改后配置文件配…

Devops系列四(使用argocd部署java应用到k8s容器)

一、说在前面的话 上文已为我们准备好了以下内容&#xff1a; 制作java应用的docker镜像&#xff0c;并推送至镜像仓库上传helm yaml代码至gitlab仓库&#xff08;此gitlab和java应用所在的gitlab可以独立&#xff0c;也可以在一起&#xff0c;但是不宜在同一个工程&#xff…

Gradio HTML组件详解

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

使用electron打包spring-boot+vue项目开发桌面exe端项目一站式全部解决!专栏有解决报错文章

准备工具 前端:node.js 14以下(直接安装 node.js 即可) 后端:jre 1.8(必须1.8) 工具: Bat_To_Exe_ConverterInno_Setup 汉化版(英文版不支持简体中文,打包出来的安装界面是英文的)我以及给大家汇总完毕直接点击进去下载即可 https://pan.baidu.com/s/1XoA0tj3b4Q…

上位机和树莓派采用USB转TTL模块连接,采用串口通信

采用USB转TTL模块&#xff0c;Linux系统的工控机接USB插口&#xff0c;树莓派的GPIO口接TTL串口&#xff0c;如何编写双向通信程序&#xff1f; USB转TTL-CH340模块 ChatGPT 下面是一个示例&#xff0c;展示了如何使用USB转TTL模块在Linux系统的工控机和树莓派之间进行双向…

Springboot分布式事务

一、先了解什么是本地事务 1. 概念 本地事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器位于同一节点相同数据库上。 又称为传统事务。它是一个操作序列&#xff0c;这些操作要么都执行&#xff0c;要么都不执行&#xff0c;是一个不可分割的工作单位。例…

【08】STM32·HAL库开发-HAL库介绍 | STM32Cube固件库介绍 | HAL库框架结构 | 如何使用HAL库及使用注意事项

目录 1.初识HAL库&#xff08;了解&#xff09;1.1CMSIS简介1.2HAL库简介 2.STM32Cube固件包浅析&#xff08;了解&#xff09;2.1如何获取STM32Cube固件包&#xff1f;2.2STM32Cube固件包文件夹简介2.3CMSIS文件夹关键文件2.3.1CMSIS标准规定软件包目录2.3.2Device和Include文…

在Windows环境下安装Elasticsearch 8.8.2

Elasticsearch是一种开源的分布式搜索和分析引擎&#xff0c;被广泛应用于构建实时搜索、日志分析、数据可视化等应用。本文将详细介绍如何在Windows环境下安装和配置Elasticsearch 8。 安装Elasticsearch 步骤1&#xff1a;准备工作 在开始安装之前&#xff0c;确保已满足以…

KMP--高效字符串匹配算法(Java)

KMP算法 KMP算法算法介绍代码演示: KMP算法 KMP算法是为了解决这一类问题,给定一个字符串str1,和一个字符串str2,如果str2属于str1d的字串,则返回字串第一个出现位置的下标,不存在返回-1. 注意: 子串是连续的. 举个例子 str1 “abc123abs” str1 长度假设m str2 “123”; str2…

QT学习笔记:TCP客户端的实现

QT一般用来做客户端&#xff0c;我这里就简单讲一下怎么开发基于QT的TCP客户端。 1、用QtCreator创建项目 2、界面 3、.pro文件添加network QT core gui network 4、mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include &l…

Mysql之账号管理、建库以及四大引擎详解

目录 一、MySql数据库引擎 1.1 什么是数据库引擎&#xff1f; 1.2 MySQL常见数据库引擎 1.2.1.InnoDB(MySQL默认引擎) 1.2.2.MyISAM 1.2.3.MEMORY&#xff08;Heap&#xff09; 1.3 存储引擎查看 二、建库 2.1.默认数据库介绍 2.2.建库 2.3.查看数据库 2.4.删除数…