Redis GEO 命令详解:轻松实现“附近的人“功能

news2025/3/29 8:01:25

目录

引言

Redis GEO命令概述

什么是GEO命令?

主要命令详解

命令应用示例

添加地点信息

查询两地距离

查询附近的城市

实现"查找附近的人"功能

功能需求与实现思路

基本需求

实现思路

命令实现方案

存储用户位置

查询附近的用户

Java代码实现详解

使用Redis GEO的优势与注意事项

优势

注意事项


引言

        在移动互联网时代,基于地理位置的服务已成为众多应用的标配功能。无论是打车软件、外卖平台还是社交应用,"附近的XX"功能几乎无处不在。这类功能的核心技术挑战在于:如何高效存储地理位置数据并进行快速检索?Redis 3.2版本引入的GEO(地理空间)命令集完美解决了这一问题,为开发者提供了简单高效的地理位置数据处理方案。

        本文将深入浅出地介绍Redis GEO命令及其工作原理,通过实际案例和代码示例,帮助你轻松实现"查找附近的人"等地理位置相关功能。无论你是Redis新手还是有经验的开发者,都能从中获取有价值的信息。


Redis GEO命令概述

什么是GEO命令?

        GEO是"Geolocation"(地理定位)的简写,Redis GEO是Redis专门为地理位置信息存储和检索设计的命令集。它允许我们将经纬度坐标存储到Redis数据库中,并支持按距离查询、计算两点间距离等多种地理空间操作。

        底层实现上,Redis GEO使用了地理空间索引算法(Geohash),将二维的经纬度转换为一维的字符串,并通过Redis的有序集合(Sorted Set)来存储,这使得地理位置的存取和计算变得非常高效。

主要命令详解

Redis GEO主要提供了以下几个核心命令:

  • GEOADD: 添加地理空间信息
# 将指定的地理空间位置(经度、纬度、名称)添加到指定的key中
# 可以一次添加多个位置
GEOADD key longitude latitude member [longitude latitude member ...]
  • GEODIST: 计算两点间距离
# 返回两个给定位置之间的距离
# unit参数指定返回值的单位,可以是m(米)、km(千米)、mi(英里)或ft(英尺)
GEODIST key member1 member2 [unit]
  • GEOHASH: 获取经纬度的Geohash表示
# 返回一个或多个位置元素的Geohash表示
# Geohash是一种将经纬度编码为字符串的方法
GEOHASH key member [member ...]
  • GEOHASH: 获取经纬度的Geohash表示
# 返回一个或多个位置元素的Geohash表示
# Geohash是一种将经纬度编码为字符串的方法
GEOHASH key member [member ...]
  • GEOPOS: 获取位置的经纬度
# 返回指定名称位置的经纬度坐标
GEOPOS key member [member ...]
  • GEORADIUS: 查找指定半径内的成员
# 以给定的经纬度为中心,返回键中包含的位置元素当中,与中心的距离不超过给定半径的所有位置元素
GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

WITHDIST: 在返回位置元素的同时,将位置元素与中心之间的距离也一并返回
WITHCOORD: 将位置元素的经度和纬度也一并返回
WITHHASH: 以52位无符号整数的形式返回位置元素的geohash值(主要用于调试)
COUNT n: 限定返回的记录数量
ASC|DESC: 根据中心的位置,按照从近到远(ASC)或从远到近(DESC)的顺序返回位置元素

  • GEOSEARCH: 在指定范围内搜索
# 在指定范围内搜索,范围可以是圆形或矩形
GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius unit] [BYBOX width height unit] [WITHDIST] [WITHCOORD] [WITHHASH] [COUNT count] [ASC|DESC]
  • GEOSEARCHSTORE: 在指定范围内搜索并将结果存储
# 与GEOSEARCH功能相同,但可以将结果存储到指定的key中
GEOSEARCHSTORE destination key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius unit] [BYBOX width height unit] [WITHDIST] [WITHCOORD] [WITHHASH] [COUNT count] [ASC|DESC]

这些命令共同构成了一个完整的地理空间数据处理工具集,能够满足大多数基于位置的服务需求。

命令应用示例

让我们通过一个具体的例子来理解GEO命令的使用:

添加地点信息

# 上述命令将"东京"和"吉隆坡"两个城市的经纬度信息添加到名为"locations"的地理空间集合中。
GEOADD locations 139.781210 35.774426 "东京" 101.653962 5.205122 "吉隆坡"

查询两地距离

# 这个命令会返回东京和吉隆坡之间的距离(单位:公里)。
GEODIST locations "东京" "吉隆坡" km

查询附近的城市

# 这个命令会查找距离指定坐标点(经度139.0,纬度35.0)1000公里范围内的所有城市,并同时返回它们与中心点的距离。
GEORADIUS locations 139.0 35.0 1000 km WITHDIST

实现"查找附近的人"功能

        "查找附近的人"是移动应用中的常见功能,下面我们将详细讲解如何使用Redis GEO命令来实现。

功能需求与实现思路

基本需求

  • 存储每个用户的地理位置信息(经纬度)
  • 能够查询指定用户周围一定范围内的其他用户
  • 返回的用户列表按照距离排序

实现思路

  1. 使用GEOADD命令将用户ID及其经纬度信息存储在Redis中
  2. 当需要查询"附近的人"时,使用GEORADIUS命令,以查询用户的位置为中心,指定半径范围进行搜索

命令实现方案

假设我们正在开发一个社交应用,需要实现广州市用户查找1000公里范围内其他用户的功能:

存储用户位置

GEOADD user_location 113.267548 23.142979 "user1"
GEOADD user_location 113.300000 23.150000 "user2"
GEOADD user_location 114.057868 22.543099 "user3"

查询附近的用户

# 命令会返回距离广州市指定坐标1000公里范围内的所有用户,并显示他们与查询点的具体距离。
GEORADIUS user_location 113.254325 23.144043 1000 km WITHDIST

Java代码实现详解

下面是使用Java语言和Jedis客户端实现"查找附近的人"功能的代码示例:

import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.GeoRadiusResponse;
import redis.clients.jedis.GeoUnit;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.GeoRadiusParam;

import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.stream.Collectors;

/**
 * Redis GEO功能示例:实现"附近的人"功能
 * 
 * @author Muller
 */
public class RedisGeoDemo {
    private static final String USER_LOCATION_KEY = "user_location";
    
    /**
     * 存储用户地理位置信息
     * 
     * @param userId 用户ID
     * @param longitude 经度
     * @param latitude 纬度
     * @param jedis Redis连接
     * @return 添加成功的数量
     */
    public static Long saveUserLocation(String userId, double longitude, double latitude, Jedis jedis) {
        try {
            return jedis.geoadd(USER_LOCATION_KEY, longitude, latitude, userId);
        } catch (Exception e) {
            System.err.println("保存用户位置信息失败: " + e.getMessage());
            return 0L;
        }
    }
    
    /**
     * 批量存储多个用户的地理位置信息
     * 
     * @param userLocations 用户位置Map,key为用户ID,value为经纬度坐标
     * @param jedis Redis连接
     * @return 添加成功的数量
     */
    public static Long saveUserLocations(Map<String, double[]> userLocations, Jedis jedis) {
        try {
            Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
            
            for (Map.Entry<String, double[]> entry : userLocations.entrySet()) {
                String userId = entry.getKey();
                double[] coordinates = entry.getValue();
                memberCoordinateMap.put(userId, new GeoCoordinate(coordinates[0], coordinates[1]));
            }
            
            return jedis.geoadd(USER_LOCATION_KEY, memberCoordinateMap);
        } catch (Exception e) {
            System.err.println("批量保存用户位置信息失败: " + e.getMessage());
            return 0L;
        }
    }
    
    /**
     * 查询附近的人
     * 
     * @param longitude 经度
     * @param latitude 纬度
     * @param radius 半径
     * @param jedis Redis连接
     * @return 附近用户ID列表
     */
    public static List<String> getNearbyUsers(double longitude, double latitude, double radius, Jedis jedis) {
        try {
            List<GeoRadiusResponse> responses = jedis.georadius(
                USER_LOCATION_KEY, 
                longitude, 
                latitude, 
                radius, 
                GeoUnit.KM, 
                GeoRadiusParam.geoRadiusParam().withDist().sortAscending()
            );
            
            return responses.stream()
                    .map(GeoRadiusResponse::getMemberByString)
                    .collect(Collectors.toList());
        } catch (Exception e) {
            System.err.println("查询附近用户失败: " + e.getMessage());
            return List.of();
        }
    }
    
    /**
     * 获取用户详细地理信息(包含距离)
     * 
     * @param longitude 经度
     * @param latitude 纬度
     * @param radius 半径
     * @param jedis Redis连接
     * @return 附近用户详细信息列表
     */
    public static List<UserGeoInfo> getNearbyUsersWithDistance(double longitude, double latitude, double radius, Jedis jedis) {
        try {
            List<GeoRadiusResponse> responses = jedis.georadius(
                USER_LOCATION_KEY, 
                longitude, 
                latitude, 
                radius, 
                GeoUnit.KM, 
                GeoRadiusParam.geoRadiusParam().withDist().withCoord().sortAscending()
            );
            
            return responses.stream()
                    .map(response -> new UserGeoInfo(
                        response.getMemberByString(),
                        response.getDistance(),
                        response.getCoordinate().getLongitude(),
                        response.getCoordinate().getLatitude()
                    ))
                    .collect(Collectors.toList());
        } catch (Exception e) {
            System.err.println("查询附近用户详细信息失败: " + e.getMessage());
            return List.of();
        }
    }
    
    /**
     * 计算两个用户之间的距离
     * 
     * @param userId1 用户1的ID
     * @param userId2 用户2的ID
     * @param jedis Redis连接
     * @return 两用户间距离(单位:公里),如果计算失败返回-1
     */
    public static double getDistanceBetweenUsers(String userId1, String userId2, Jedis jedis) {
        try {
            Double distance = jedis.geodist(USER_LOCATION_KEY, userId1, userId2, GeoUnit.KM);
            return distance != null ? distance : -1;
        } catch (Exception e) {
            System.err.println("计算用户距离失败: " + e.getMessage());
            return -1;
        }
    }
    
    /**
     * 获取用户的地理坐标
     * 
     * @param userId 用户ID
     * @param jedis Redis连接
     * @return 用户坐标[经度,纬度],如果不存在返回null
     */
    public static double[] getUserPosition(String userId, Jedis jedis) {
        try {
            List<GeoCoordinate> positions = jedis.geopos(USER_LOCATION_KEY, userId);
            
            if (positions != null && !positions.isEmpty() && positions.get(0) != null) {
                GeoCoordinate pos = positions.get(0);
                return new double[] { pos.getLongitude(), pos.getLatitude() };
            }
            
            return null;
        } catch (Exception e) {
            System.err.println("获取用户坐标失败: " + e.getMessage());
            return null;
        }
    }
    
    /**
     * 用户地理信息包装类
     */
    public static class UserGeoInfo {
        private String userId;
        private double distance;
        private double longitude;
        private double latitude;
        
        public UserGeoInfo(String userId, double distance, double longitude, double latitude) {
            this.userId = userId;
            this.distance = distance;
            this.longitude = longitude;
            this.latitude = latitude;
        }
        
        public String getUserId() {
            return userId;
        }
        
        public double getDistance() {
            return distance;
        }
        
        public double getLongitude() {
            return longitude;
        }
        
        public double getLatitude() {
            return latitude;
        }
        
        @Override
        public String toString() {
            return "用户ID: " + userId + 
                   ", 距离: " + String.format("%.2f", distance) + "公里" +
                   ", 坐标: [" + longitude + ", " + latitude + "]";
        }
    }
    
    /**
     * 示例用法
     */
    public static void main(String[] args) {
        // 这里仅用于演示,实际使用应通过连接池获取Jedis实例
        try (Jedis jedis = new Jedis("localhost", 6379)) {
            // 清除之前可能存在的测试数据
            jedis.del(USER_LOCATION_KEY);
            
            // 存储几个测试用户的位置(广州及周边城市的坐标)
            saveUserLocation("user1", 113.267548, 23.142979, jedis);  // 广州
            saveUserLocation("user2", 114.057868, 22.543099, jedis);  // 深圳
            saveUserLocation("user3", 113.030396, 22.938259, jedis);  // 佛山
            saveUserLocation("user4", 116.397128, 39.916527, jedis);  // 北京
            
            System.out.println("===== 查询广州周边1000公里范围内的用户 =====");
            List<String> nearbyUsers = getNearbyUsers(113.267548, 23.142979, 1000, jedis);
            System.out.println("附近的用户: " + nearbyUsers);
            
            System.out.println("\n===== 查询广州周边1000公里范围内的用户(包含距离信息) =====");
            List<UserGeoInfo> nearbyUsersWithDist = getNearbyUsersWithDistance(113.267548, 23.142979, 1000, jedis);
            nearbyUsersWithDist.forEach(System.out::println);
            
            System.out.println("\n===== 计算用户间距离 =====");
            double distance = getDistanceBetweenUsers("user1", "user2", jedis);
            System.out.println("广州(user1)到深圳(user2)的距离: " + String.format("%.2f", distance) + "公里");
            
            distance = getDistanceBetweenUsers("user1", "user4", jedis);
            System.out.println("广州(user1)到北京(user4)的距离: " + String.format("%.2f", distance) + "公里");
            
            System.out.println("\n===== 获取用户坐标 =====");
            double[] pos = getUserPosition("user1", jedis);
            if (pos != null) {
                System.out.println("用户user1的坐标: [" + pos[0] + ", " + pos[1] + "]");
            }
        }
    }
}

使用Redis GEO的优势与注意事项

优势

  • 性能高效:Redis基于内存操作,地理位置查询性能极高
  • 使用简单:GEO命令集设计直观,容易上手
  • 功能完善:提供了从添加、查询到计算距离的完整功能集
  • 可扩展性好:可以轻松处理百万级别的POI(兴趣点)数据
  • 与Redis其他功能协同:可以结合Redis的缓存、事务等功能

注意事项

  • 精度限制:GEO命令的精度受到Geohash算法的限制,对于需要极高精度的应用场景(如军事)可能不适用
  • 内存消耗:大量GEO数据会占用较多内存,需要合理规划Redis服务器资源
  • 经纬度范围:Redis GEO只接受有效的经纬度范围(经度:-180到180,纬度:-85.05112878到85.05112878)
  • 数据持久化:使用AOF持久化模式可能会导致重启时间延长,需权衡数据安全性和重启速度
  • 适用场景:最适合"附近的XX"这类不需要复杂地理形状计算的场景,如需多边形区域计算等高级地理信息功能,可能需要专业GIS系统

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

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

相关文章

基于springboot的校园资料分享平台(048)

摘要 随着信息互联网购物的飞速发展&#xff0c;国内放开了自媒体的政策&#xff0c;一般企业都开始开发属于自己内容分发平台的网站。本文介绍了校园资料分享平台的开发全过程。通过分析企业对于校园资料分享平台的需求&#xff0c;创建了一个计算机管理校园资料分享平台的方案…

CS2 demo manager 安装

CS2DM CS Demo Managerhttps://cs-demo-manager.com/PostgreSQL&#xff08;CS2DM需要17以上&#xff09; EDB: Open-Source, Enterprise Postgres Database Managementhttps://www.enterprisedb.com/downloads/postgres-postgresql-downloads 新CS2dm现在打开是这样的&…

奇怪的异形选项卡样式、弧形边框选项卡

<template><div :class"$options.name"><div class"tab">默认选项卡</div><div class"tab" active>选中选项卡</div><el-divider /><el-tabs v-model"tabActiveName" tab-click"(t…

3.23 代码随想录第二十四天打卡

122.买卖股票的最佳时机II (1)题目描述: (2)解题思路: class Solution { public:int maxProfit(vector<int>& prices) {int result 0;for (int i 1; i < prices.size(); i) {result max(prices[i] - prices[i - 1], 0);}return result;} }; (3)总结: 1.假…

Python---数据分析(Pandas十一:二维数组DataFrame统计计算二)

1、std 用于计算 DataFrame 中数值的标准差。 DataFrame.std(axis0, skipnaTrue, ddof1, numeric_onlyFalse, **kwargs) 描述说明axis {0 或 ‘index’, 1 或 ‘columns’, None}, 默认为 0。这个参数决定了计算标准差是在哪个轴上进行&#xff1a; 如果 axis0 或 axisindex&…

OpenCV平滑处理:图像去噪与模糊技术详解

引言 在图像处理中&#xff0c;噪声是一个常见的问题&#xff0c;它可能来自于图像采集设备、传输过程或环境干扰。为了去除噪声并改善图像质量&#xff0c;平滑处理&#xff08;Smoothing&#xff09;是一种常用的技术。OpenCV提供了多种平滑处理方法&#xff0c;包括均值滤波…

使用Python将视频转化为gif

使用Python将视频转化为gif 一、前言二、准备三、测试 一、前言 最近想把喜欢的视频片段作成gif&#xff0c;就试着用Python做了下&#xff0c;感觉效果还行&#xff0c;这里做个记录。 二、准备 先下载安装对应的库&#xff0c;命令如下&#xff1a; pip install moviepy …

HTTP长连接与短连接的前世今生

HTTP长连接与短连接的前世今生 大家好&#xff01;作为一名在互联网摸爬滚打多年的开发者&#xff0c;今天想跟大家聊聊HTTP中的长连接和短连接这个话题。 记得我刚入行时&#xff0c;对这些概念一头雾水&#xff0c;希望这篇文章能帮助新入行的朋友少走些弯路。 什么是HTTP…

批量将 PPT 文档中的图片提取到文件夹

在 PPT 文档中我们可以插入很多的图片来丰富我们的幻灯片页面&#xff0c;但是当我们需要将 PPT 幻灯片中的图片提取出来的时候&#xff0c;会非常的麻烦&#xff0c;因为我们需要打开 PPT 然后将图片保存起来。会非常的耗费我们的时间和精力。今天给大家介绍的就是一种批量将 …

yolo目标检测算法在DJI上的研究分析(大纲)

yolo目标检测算法在DJI上的研究分析 面向边缘计算的实时目标检测系统设计与部署 第一章 绪论 1.1 研究背景与意义 目标检测技术需求&#xff1a; DJI设备&#xff08;如无人机、摄像头&#xff09;在安防、巡检、农业等场景中的广泛应用现有YOLO算法在高分辨率图像或资源受限…

MyBatisPlus(SpringBoot版)学习第二讲:基本CRUD

目录 1.BaseMapper 2. 基本CRUD 1. 插入一条记录 2. 删除 1>. 根据ID删除 2>. 根据实体&#xff08;ID&#xff09;删除 3>. 根据columnMap条件删除 4>. 根据entity条件删除 5>. 根据ID批量删除 3. 修改 1>. 根据ID修改 2>. 根据whereEntity条…

基于Spring Boot的企业内管信息化系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

Bitcoin Thunderbolt 内测通道开启,加速比特币交易新时代

比特币作为全球领先的加密货币&#xff0c;一直占据着去中心化金融的核心地位。然而&#xff0c;随着比特币生态的不断扩展&#xff0c;其交易速度和扩容问题逐渐成为制约发展的关键瓶颈。为解决这一难题&#xff0c; 比特币雷电网络&#xff08;Bitcoin Thunderbolt&#xff0…

QT笔记----QCheckBox

文章目录 概要1、QCheckBox 的基本概念2、单个QCheckBox3、多个QCheckBox同时应用3.1、实现效果3.2、实现Demo 概要 在 Qt 应用程序开发中&#xff0c;QCheckBox 是一个常用的用户界面元素&#xff0c;它允许用户在两种状态&#xff08;选中和未选中&#xff09;之间进行切换&a…

GR00T N1——英伟达开源的通用人形VLA:类似Helix的快与慢双系统,且可类似ViLLA利用海量的无标注视频做训练

前言 就在昨天3.19日的凌晨&#xff0c;英伟达发布的GR00T N1还是很有含金量的(上午已有好几个朋友私我了)&#xff0c;由此可以看到很多相关工作的影子&#xff0c;比如helix π0 LAPA&#xff0c;具体而言&#xff0c;其具有双系统架构 VLM模块(系统2)通过视觉和语言指令解…

数据建模流程: 概念模型>>逻辑模型>>物理模型

数据建模流程 概念模型 概念模型是一种高层次的数据模型&#xff0c;用于描述系统中的关键业务概念及其之间的关系。它主要关注业务需求和数据需求&#xff0c;而不涉及具体的技术实现细节。概念模型通常用于在项目初期帮助业务人员和技术人员达成共识&#xff0c;确保对业务需…

光谱仪与光谱相机的核心区别与协同应用

一、核心功能与数据维度 ‌光谱仪‌ ‌功能定位‌&#xff1a;专注单点或线状区域的光谱分析&#xff0c;通过色散元件&#xff08;光栅/棱镜&#xff09;分离波长&#xff0c;生成一维或二维光谱曲线&#xff0c;用于量化光强、吸收率等参数‌。 ‌数据维度‌&#xff1a;输…

运行时智控:PanLang 开发者指南(一)运行时系统核心模块实现——PanLang 原型全栈设计方案与实验性探索5

运行时智控&#xff1a;PanLang 开发者指南&#xff08;一&#xff09;运行时系统核心模块实现——PanLang 原型全栈设计方案与实验性探索5 文章目录 运行时智控&#xff1a;PanLang 开发者指南&#xff08;一&#xff09;运行时系统核心模块实现——PanLang 原型全栈设计方案与…

操作系统导论——第13章 抽象:地址空间

一、早期系统 从内存来看&#xff0c;早期的机器并没有提供多少抽象给用户。基本上&#xff0c;机器的物理内存如图13.1所示 操作系统曾经是一组函数&#xff08;实际上是一个库&#xff09;&#xff0c;在内存中&#xff08;在本例中&#xff0c;从物理地址0开始&#xff09;&…

C# 调用 VITS,推理模型 将文字转wav音频net8.0 跨平台

一、系统环境 操作系统&#xff1a;win10&#xff0c;win11 运行环境&#xff1a;dotnet8 工具:命令行&#xff0c;powershell 开源库:sherpa-onnx 二、工具和源码下载 开源库:https://k2-fsa.github.io/sherpa/onnx/index.html 运行环境下载 https://dotnet.microsoft.c…