【Java、Redis】通过中心经纬度与半径获取范围内的结果集(类似附近的人)

news2025/1/19 20:41:53

文章目录

  • 需求
  • 解决方案
  • 什么是Redis + GeoHash
  • Java实现
    • InitEquLongLatTask.java
    • Controller
    • service
    • xml sql语句
    • 引用的pom依赖

需求

通过百度地图的覆盖物功能,用户在页面上画圈选定某个区域,前端传输中心点经纬度与半径给后端,后端需要返回位置在圈内的设备

在这里插入图片描述

解决方案

经过网上查阅资料,最终决定使用Redis + GeoHash来做,效率也很高

什么是Redis + GeoHash

redis 实现附近的人功能主要通过Geo模块的六个命令

关键字解释
GEOADD将给定的位置对象(纬度、经度、名字)添加到指定的key
GEOPOS从key里面返回所有给定位置对象的位置(经度和纬度)
GEODIST返回两个给定位置之间的距离
GEOHASH返回一个或多个位置对象的Geohash表示
GEORADIUS以给定的经纬度为中心,返回目标集合中与中心的距离不超过给定最大距离的所有位置对象
GEORADIUSBYMEMBER以给定的位置对象为中心,返回与其距离不超过给定最大距离的所有位置对象

以GEOADD 命令和GEORADIUS 命令简单举例:

GEOADD key longitude latitude member [longitude latitude member …]

其中,key为集合名称,member为该经纬度所对应的对象。
GEOADD 添加多个商户“火锅店”位置信息:

GEOADD hotel 119.98866180732716 30.27465803229662 火锅店

GEORADIUS 根据给定的经纬度为中心,获取目标集合中与中心的距离不超过给定最大距离(500米内)的所有位置对象,也就是“附近的人”。

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] [STORE key] [STORedisT key]

范围单位:m | km | ft | mi --> 米 | 千米 | 英尺 | 英里

关键字解释
WITHDIST在返回位置对象的同时,将位置对象与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致
WITHCOORD将位置对象的经度和维度也一并返回
WITHHASH以 52 位有符号整数的形式,返回位置对象经过原始 geohash 编码的有序集合分值。这个选项主要用于底层应用或者调试,实际中的作用并不大
ASC DESC从近到远返回位置对象元素 、从远到近返回位置对象元素
COUNT count选取前N个匹配位置对象元素。(不设置则返回所有元素)
STORE key将返回结果的地理位置信息保存到指定key
STORedisT key将返回结果离中心点的距离保存到指定key

例如下边命令:获取当前位置周边500米内的所有饭店

GEORADIUS hotel 119.98866180732716 30.27465803229662 500 m WITHCOORD

Redis内部使用有序集合(zset)保存用户的位置信息,zset中每个元素都是一个带位置的对象,元素的score值为通过经、纬度计算出的52位geohash值

Java实现

建立一个任务,当项目启动时,把大量设备的经纬度信息存入redis中

InitEquLongLatTask.java

/**
 * 项目启动初始化设备的经纬度到redis中(以圆形中心经纬度 获取在中心半径以内的设备数据)
 *
 * @author xiegege
 * @date 2023/1/12/012 14:21
 */
@Service
public class InitEquLongLatTask implements ApplicationListener<ContextRefreshedEvent> {

    private final static Logger logger = LogManager.getLogger(InitEquLongLatTask.class);
    
    public final static String REDIS_KEY = "equLongLat-task";

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private IbmsEqdBasicDao eqdBasicDao;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext context = event.getApplicationContext().getParent();
        if (context == null) {
            logger.info("++++++++ 开始:初始化设备的经纬度到redis中 ++++++++");
            int size = 0;
            try {
                // 删除该key的所有值
                redisTemplate.delete(REDIS_KEY);
                List<IbmsEqdBasic> eqdBasicList = eqdBasicDao.getAllEquLongLat();
                eqdBasicList.forEach(item -> {
                    // 将用户地理位置信息存入 Redis
                    RedisGeoCommands.GeoLocation geoLocation =
                            new RedisGeoCommands.GeoLocation(
                                    item.getEqudatasysid(),
                                    // Point(经度, 纬度)
                                    new Point(Double.parseDouble(item.getGislong()), Double.parseDouble(item.getGislat()))
                            );
                    redisTemplate.opsForGeo().add(REDIS_KEY, geoLocation);
                });
                size = eqdBasicList.size();
            } catch (Exception e) {
                logger.error("++++++++ 失败:初始化设备的经纬度到redis中 ++++++++", e);
            }
            logger.info("++++++++ 结束:初始化设备的经纬度到redis中【共" + size + "台设备】 ++++++++");
        }
    }
}

Controller

@Controller
@RequestMapping(value = "${apiPath}/hp/heavyProtect")
public class HeavyProtectController extends BaseController {

    @Autowired
    private HeavyProtectService heavyProtectService;

    /**
     * 获取命中的设备经纬度
     *
     * @param gislong 圆心经度
     * @param gislat  圆心纬度
     * @param radius  半径/米
     * @return 命中集合
     */
    @RequestMapping("getHitEquList")
    @ResponseBody
    public Map<String, Object> getHitEquList(String gislong, String gislat, Integer radius) {
        if (StringUtils.isBlank(gislong)) {
            return error("请传入经度");
        }
        if (StringUtils.isBlank(gislat)) {
            return error("请传入纬度");
        }
        if (radius == null) {
            return error("请传入半径");
        }
        // (a.gislong >= -180 and a.gislong <= 180) and (a.gislat >= -85.05112878 and a.gislat <= 85.05112878)
        double gislongD = Double.parseDouble(gislong);
        double gislatD = Double.parseDouble(gislat);
        logger.info("getHitEquList=====经度:{}=====纬度:{}", gislongD, gislatD);
        if (!(gislongD >= -180 && gislongD <= 180)) {
            return error("经度不合法");
        }
        if (!(gislatD >= -85.05112878 && gislatD <= 85.05112878)) {
            return error("纬度不合法");
        }
        List<IbmsEqdBasic> hitEquList = heavyProtectService.getHitEquList(gislongD, gislatD, radius);
        return success("获取数据成功", hitEquList);
    }
}

service

@Service
@Transactional(readOnly = true)
public class HeavyProtectService {

    private static final Logger logger = LoggerFactory.getLogger(HeavyProtectService.class);

    @Autowired
    private RedisTemplate redisTemplate;

    public List<IbmsEqdBasic> getHitEquList(Double gislong, Double gislat, Integer radius) {
        List<IbmsEqdBasic> hitEquList = new ArrayList<>();
        // 初始化 Geo 命令参数对象
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();
        // 限制50台设备,包含距离,按由近到远排序
        // args.limit(50).includeDistance().sortAscending();
        args.includeDistance().sortAscending();
        // 经纬度为圆心,范围前端传来的半径/米
        Point point = new Point(gislong, gislat);
        // 初始化距离对象,单位/米
        Distance distance = new Distance(radius, RedisGeoCommands.DistanceUnit.METERS);
        Circle circle = new Circle(point, distance);
        // 获取附近的设备 GeoLocation 信息
        GeoResults<RedisGeoCommands.GeoLocation> geoResults = redisTemplate.opsForGeo().radius(InitEquLongLatTask.REDIS_KEY, circle, args);
        List<GeoResult<RedisGeoCommands.GeoLocation>> contentList = geoResults.getContent();
        if (CollectionUtils.isNotEmpty(contentList)) {
            // 封装数据
            contentList.forEach(content -> {
                RedisGeoCommands.GeoLocation<String> geoLocation = content.getContent();
                IbmsEqdBasic eqdBasic = new IbmsEqdBasic();
                eqdBasic.setEqudatasysid(geoLocation.getName());
                // 获取距离
                // double dist = content.getDistance().getValue();
                // 四舍五入精确到小数点后 1 位,方便客户端显示
                // String distanceStr = NumberUtil.round(dist, 1).toString() + "m";
                // eqdBasic.setDistance(distanceStr);
                hitEquList.add(eqdBasic);
            });
        }
        logger.info("getHitEquList=====命中设备{}台", hitEquList.size());
        // 设备id结果集,可根据结果集封装更详细的信息
        return hitEquList;
    }
}

xml sql语句

<select id="getAllEquLongLat" resultType="IbmsEqdBasic">
        select
            equdatasysid,
            gislong,
            gislat
        from ibms_brm_eqm_eqd_basic a
        where a.is_del='0'
        and a.gislong is not null
        and a.gislat is not null
        <![CDATA[
            and (a.gislong >= -180 and a.gislong <= 180)
            and (a.gislat >= -85.05112878 and a.gislat <= 85.05112878)
        ]]>
<!--        -180 <= a.gislong <= 180-->
<!--        -85.05112878 <= a.gislat <= 85.05112878-->
</select>

引用的pom依赖

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.3.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

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

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

相关文章

表格存储 Tablestore 十年发展总结

作者&#xff1a;周赵锋 阿里云基础产品团队 ​表格存储Tablestore上线已有十年&#xff0c;随着业务规模变大&#xff0c;稳定性挑战也随之而来&#xff0c;需要不断优化架构来提升可用性。本文将为大家分享表格存储Tablestore在技术层面近年来的功能演进、技术架构演进以及稳…

与哈希函数有关的结构:布隆过滤器、一致性哈希

1、认识哈希函数 &#xff08;out f(in data)&#xff09; 输入参数in&#xff0c;其值域范围可以看作是无穷大的。输出函数out&#xff0c;其值域范围可能性很大&#xff0c;但是一定是有穷尽的哈希函数没有任何随机的机制&#xff0c;固定的输入一定是固定的输出输入无穷多但…

计算机基础——无处不网络

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.计算机网络概述 1.计算机网络发展史 二.计算机网络应用领域 三.计算机网…

基于YOLOv6m的接打电话检测识别分析系统

本身在实际项目开发应用中YOLO都是目标检测任务的绝对主力&#xff0c;从v3一直跟着到了v7&#xff0c;做了很多的项目&#xff0c;处理了很多的数据&#xff0c;当然了也积累了一些自己的成果和心得&#xff0c;这里主要是以不常用到的yolov6m系列的模型来开发构建接打电话行为…

python基础篇之函数

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a;lqj_本人的博客_CSDN博客-微信小程序,前端,vue领域博主lqj_本人擅长微信小程序,前端,vue,等方面的知识https://blog.csdn.net/lbcyllqj?spm1000.2115.3001.5343 哔哩哔哩欢迎关注&…

最简最速搭建grpc分布式服务的Mac系统开发环境

文章目录环境详情基本原理什么是 Protobuf工具安装环境搭建编写服务类的实现启动服务客户端测试环境详情 golang 1.18 macOS Big Sur protobuf 3 基本原理 整个RPC过程就是&#xff1a; 客户端 发送 数据&#xff08;以字节流的方式&#xff09;服务端接收&#xff0c;并…

Spring Boot 大型线上商城项目实战教程试学(文末视频版)

视频链接在文末 在学习一门技术的时候&#xff0c;相信很多开发者会在开源网站上寻找对应技术栈的开源项目&#xff0c;通过阅读源码&#xff0c;学习项目作者的开发思路、解决问题的方法&#xff0c;这一过程&#xff0c;对大多人来说没那么容易&#xff0c;要么一开始不知从…

共享模型之管程(八)

1.线程的活跃性 1>.定义: 线程内的有限代码因为某种原因一直无法执行完毕(/执行不完); 1.1.线程活跃性的现象-死锁 1>.有这样的情况&#xff1a;一个线程需要同时获取多把锁,这时就容易发生死锁; 2>.案例 ①.t1线程已经获得A对象锁,接下来想获取B对象的锁; ②.t2线…

Python和MySQL对比(4):用Pandas 实现MySQL的行列转换语法效果

文章目录一、前言二、语法对比数据表concat&#xff08;多列合并为一列&#xff09;group_concat&#xff08;多行合并为一行&#xff09;一列拆分为多列一行拆分为多行多行转为多列多列转为多行三、小结一、前言 环境&#xff1a; windows11 64位 Python3.9 MySQL8 pandas1.4.…

【Linux】make/Makefile的简单使用

人生的态度是&#xff0c;抱最大的希望&#xff0c;尽最大的努力&#xff0c;做最坏的打算。 – 柏拉图 《理想国》 目录一.Linux项目自动化构建工具-make/Makefile1.为什么需要使用make/Makefile2.简单理解make和Makefile3.如何编写Makefile文件3.1生成可执行程序&#xff1a…

智算中心掀落地热潮,加速AI普惠化

11日&#xff0c;国家信息中心与浪潮信息联合发布的《智能计算中心创新发展指南》显示&#xff0c;目前全国有超过30个城市正在建设或提出建设智算中心&#xff0c;“十四五”期间&#xff0c;对智算中心的投资可带动人工智能核心产业增长约2.9-3.4倍。 《科创板日报》记者注意…

python+人脸识别+opencv实现真实人脸驱动的阿凡达(中)

目录一、前言二、消除图片拼接缝隙三、基于一张图片正脸转侧脸的实现1、人体头部的二维成像知识2、用特征点驱动的方法实现侧脸2.1python核心代码三、后续工作一、前言 我们在上篇名叫python人脸识别opencv实现真实人脸驱动的阿凡达&#xff08;上博文里已经实现了基于三角映射…

教资报名啦 有问题欢迎评论区提问~

报考小tips&#xff1a; ①一次选报你所有准备报考的学科 ②看好类别&#xff0c;有些是“音体美专业”专属 ③审核前如果发现有漏报的科目&#xff0c;可以先取消再报 ④一旦审核通过&#xff0c;就不能更改/增加/较少报考科目 ⑤缴费成功后&#xff0c;不退费【如果考试前几天…

德云社、本山传媒齐聚辽宁卫视春晚,郭德纲和本山大叔会参加吗

腊月二十九&#xff0c;屋里走。不得不说辽宁卫视会选时间&#xff0c;兔年的春晚定在了农历腊月二十九这一天。备受关注的辽宁卫视春晚&#xff0c;准备在农历的腊月二十九晚上七点三十分&#xff0c;正式与广大观众见面。 辽宁卫视春晚之所以倍受期待&#xff0c;是因为这些年…

【MySQL】深入理解MySQL事务(上篇)

MySQL事务前言事务的ACID 特性事务提交方式事务常见操作方式正常演示 - 证明事务的开始与回滚非正常演示1 - 证明未commit&#xff0c;客户端崩溃&#xff0c;MySQL自动会回滚&#xff08;隔离级别设置为读未提交&#xff09;非正常演示2 - 证明commit了&#xff0c;客户端崩溃…

2021年大数据挑战赛A题智能运维中的异常检测与趋势预测求解全过程论文及程序

2021年大数据挑战赛 A题 智能运维中的异常检测与趋势预测 原题再现&#xff1a; 异常检测&#xff08;异常诊断/发现&#xff09;、异常预测、趋势预测&#xff0c;是智能运维中首当其冲需要解决的问题。这类问题是通过业务、系统、产品直接关联的 KPI 业务指标进行分析诊断&…

【Linux】生产者消费者

生产者消费者 生产者消费者问题概述 生产者/消费者问题&#xff0c;也被称作有限缓冲问题。可以描述为&#xff1a;两个或者更多的线程共享同一个缓冲 区&#xff0c;其中一个或多个线程作为“生产者”会不断地向缓冲区中添加数据&#xff0c;另一个或者多个线程作为“消费者”…

优先级队列--堆的应用(堆排序与TopK问题)

堆排序&#xff1a;比较方式为小于建大堆 priority_queue(Iterator first, Iterator last): _con(first, last) // 1、使用vector的区间构造函数来初始化_con{// 2、建堆&#xff1a;从完全二叉树的最后一个非叶子结点来进行向下调整for (int i (size() - 2) / 2; i > 0; i…

2023真无线蓝牙耳机怎么选?值得入手的蓝牙耳机推荐

蓝牙耳机作为近几年备受人们欢迎的数码产品&#xff0c;很多人都想买到一款适合自己的蓝牙耳机。但&#xff0c;随着蓝牙耳机的快速发展&#xff0c;蓝牙耳机市场充斥着各种机型&#xff0c;它们有着不同的性能、价格、外观等。所以&#xff0c;不少人都有一个疑惑&#xff0c;…

玩转 MySQL Shell 沙盒实例

什么是沙盒实例&#xff1f; 沙盒实例仅适用于出于测试目的在本地计算机上部署和运行&#xff0c;可以与 InnoDB Cluster 、 InnoDB ClusterSet 和 InnoDB ReplicaSet 一起工作。 如何使用部署沙盒的 API 函数&#xff1f; 语法dba.deploySandboxInstance(port[, options])解…