Redis GEO分析和使用

本文详细介绍了Redis的GEO功能,用于存储和检索地理位置信息。通过GEOADD、GEOPOS、GEODIST等命令,实现添加、获取和计算距离等功能。并展示了如何在Java中使用Spring Data Redis接口操作RedisGeo,进行范围查询和获取地理位置集合。此外,还提供了测试用例来演示这些操作。
摘要由CSDN通过智能技术生成

需求分析:

在APP上显示距离自己当前位置最近的5个项目,这个我们可以利用redis的GEO地理定位计算可以得出,数据库中存放项目的经纬度(坐标),通过geo计算得出距离。

关于 Redis Geo介绍

1、Redis 的 Geo 是在 3.2 版本才有的
2、使用 geohash 保存地理位置的坐标
3、使用有序集合(zset)保存地理位置的集合

关于GEO的6个操作命令

1、GEOADD:增加某个地理位置的坐标
2、GEOPOS:获取某个地理位置的坐标
3、GEODIST:获取两个地理位置的距离
4、GEORADIUS:根据给定地理位置坐标获取指定范围内的地理位置集合
5、GEORADIUSBYMEMBER:根据给定地理位置获取指定范围内的地理位置集合
6、GEOHASH:获取某个地理位置的 geohash 值

命令详情解析

1.1 GEOADD命令

将给定的空间元素(纬度、经度、名字)添加到指定的键里面。 这些数据会以有序集合的形式被储存在键里面, 从而使得像 GEORADIUS 和 GEORADIUSBYMEMBER 这样的命令可以在之后通过位置查询取得这些元素。

命令demo: GEOADD key longitude latitude member [longitude latitude member …]

命令描述:将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。

返回值:添加到sorted set元素的数目,但不包括已更新score的元素。

2.1 GEODIST概念

返回两个给定位置之间的距离。如果两个位置之间的其中一个不存在, 那么命令返回空值。

指定单位的参数 unit 必须是以下单位的其中一个:

m 表示单位为米、km 表示单位为千米、mi 表示单位为英里、ft 表示单位为英尺。

如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。

GEODIST 命令在计算距离时会假设地球为完美的球形,在极限情况下, 这一假设最大会造成 0.5% 的误差。

命令demo: GEODIST key member1 member2 [unit]

3.1 GEOPOS概念

从键里面返回所有给定位置元素的位置(经度和纬度)。因为 GEOPOS 命令接受可变数量的位置元素作为输入, 所以即使用户只给定了一个位置元素, 命令也会返回数组回复。GEOPOS 命令返回一个数组, 数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度。 当给定的位置元素不存在时, 对应的数组项为空值。

命令demo: GEOPOS key member [member …]

4.1 GEOHASH概念

返回一个或多个位置元素的 Geohash 表示。返回值:一个数组, 数组的每个项都是一个 geohash 。 命令返回的 geohash 的位置与用户给定的位置元素的位置一一对应。

命令demo: GEOHASH key member [member …]

5.1 GEORADIUS概念

以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。范围可以使用以下其中一个单位:m 表示单位为米。km 表示单位为千米。mi 表示单位为英里。ft 表示单位为英尺。

在给定以下可选项时, 命令会返回额外的信息:

WITHDIST : 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
WITHCOORD : 将位置元素的经度和维度也一并返回。
WITHHASH : 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。

命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:

ASC : 根据中心的位置, 按照从近到远的方式返回位置元素。
DESC : 根据中心的位置, 按照从远到近的方式返回位置元素。

在默认情况下, GEORADIUS 命令会返回所有匹配的位置元素。 虽然用户可以使用 COUNT 选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT 选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT 选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的。

GEORADIUS 命令返回一个数组, 具体来说:在没有给定任何 WITH 选项的情况下, 命令只会返回一个像 [“New York”,“Milan”,“Paris”] 这样的线性(linear)列表。在指定了 WITHCOORD 、 WITHDIST 、 WITHHASH 等选项的情况下, 命令返回一个二层嵌套数组, 内层的每个子数组就表示一个元素。在返回嵌套数组时, 子数组的第一个元素总是位置元素的名字。 至于额外的信息, 则会作为子数组的后续元素, 按照以下顺序被返回:以浮点数格式返回的中心与位置元素之间的距离, 单位与用户指定范围时的单位一致。

6.1 GEORADIUSBYMEMBER概念

这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点。

相关代码
接口层:

package com.shengbangtec.service;

import com.shengbangtec.model.CityInfo;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;

import java.util.Collection;
import java.util.List;


public interface GeoService {

    /**
     * <h2>把城市信息保存到 Redis 中</h2>
     * @param cityInfos {@link CityInfo}
     * @return 成功保存的个数
     * */
    Long saveCityInfoToRedis(Collection<CityInfo> cityInfos, String GEO_KEY);

    /**
     * <h2>获取给定城市的坐标</h2>
     * @param cities 给定城市 key
     * @return {@link Point}s
     * */
    List<Point> getCityPos(String[] cities,String GEO_KEY);

    /**
     * <h2>获取两个城市之间的距离</h2>
     * @param city1 第一个城市
     * @param city2 第二个城市
     * @param metric {@link Metric} 单位信息, 可以是 null
     * @return {@link Distance}
     * */
    Distance getTwoCityDistance(String city1, String city2, Metric metric,String GEO_KEY);

    /**
     * <h2>根据给定地理位置坐标获取指定范围内的地理位置集合</h2>
     * @param within {@link Circle} 中心点和距离
     * @param args {@link RedisGeoCommands.GeoRadiusCommandArgs} 限制返回的个数和排序方式, 可以是 null
     * @return {@link RedisGeoCommands.GeoLocation}
     * */
    GeoResults<RedisGeoCommands.GeoLocation<String>> getPointRadius(
            Circle within, RedisGeoCommands.GeoRadiusCommandArgs args,String GEO_KEY);

    /**
     * <h2>根据给定地理位置获取指定范围内的地理位置集合</h2>
     * */
    GeoResults<RedisGeoCommands.GeoLocation<String>> getMemberRadius(
            String member, Distance distance, RedisGeoCommands.GeoRadiusCommandArgs args,String GEO_KEY);

    /**
     * <h2>获取某个地理位置的 geohash 值</h2>
     * @param cities 给定城市 key
     * @return city geohashs
     * */
    List<String> getCityGeoHash(String[] cities,String GEO_KEY);

}

实现层:

package com.shengbangtec.service.impl;

import com.shengbangtec.model.CityInfo;
import com.shengbangtec.service.GeoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
@Slf4j
public class GeoServiceImpl implements GeoService {

    /** redis 客户端 */
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public Long saveCityInfoToRedis(Collection<CityInfo> cityInfos, String GEO_KEY) {
        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        Set<RedisGeoCommands.GeoLocation<String>> locations = new HashSet<>();
        cityInfos.forEach(ci -> locations.add(new RedisGeoCommands.GeoLocation<String>(
                ci.getProjectName(), new Point(ci.getLongitude(), ci.getLatitude())
        )));
        return ops.add(GEO_KEY, locations);
    }

    @Override
    public List<Point> getCityPos(String[] cities,String GEO_KEY) {
        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        return ops.position(GEO_KEY, cities);
    }

    @Override
    public Distance getTwoCityDistance(String city1, String city2, Metric metric,String GEO_KEY) {
        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        return metric == null ?
                ops.distance(GEO_KEY, city1, city2) : ops.distance(GEO_KEY, city1, city2, metric);
    }

    @Override
    public GeoResults<RedisGeoCommands.GeoLocation<String>> getPointRadius(
            Circle within, RedisGeoCommands.GeoRadiusCommandArgs args,String GEO_KEY
    ) {
        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        return args == null ?
                ops.radius(GEO_KEY, within) : ops.radius(GEO_KEY, within, args);
    }

    @Override
    public GeoResults<RedisGeoCommands.GeoLocation<String>> getMemberRadius(
            String member, Distance distance, RedisGeoCommands.GeoRadiusCommandArgs args,String GEO_KEY
    ) {

        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        return args == null ?
                ops.radius(GEO_KEY, member, distance) : ops.radius(GEO_KEY, member, distance, args);
    }

    @Override
    public List<String> getCityGeoHash(String[] cities,String GEO_KEY) {
        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        return ops.hash(GEO_KEY, cities);
    }

}

测试类

package com.shengbangtec;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.shengbangtec.config.AppConfig;
import com.shengbangtec.mapper.CompanyMapper;
import com.shengbangtec.mapper.ProjectMapper;
import com.shengbangtec.mapper.YdzfUserMapper;
import com.shengbangtec.model.CompanyDistance;
import com.shengbangtec.model.ProjectDistance;
import com.shengbangtec.model.CityInfo;
import com.shengbangtec.service.GeoService;
import com.shengbangtec.utils.MapUtil;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import cn.smallbun.screw.core.Configuration;
import cn.smallbun.screw.core.engine.EngineConfig;
import cn.smallbun.screw.core.engine.EngineFileType;
import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute;
import cn.smallbun.screw.core.process.ProcessConfig;


@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {


    private List<CityInfo> cityInfos;
    @Autowired
    private GeoService geoService;
    @Autowired
    private ProjectMapper projectMapper;
    @Autowired
    private CompanyMapper companyMapper;

    @Before
    public void init() {
        cityInfos = projectMapper.getProjectDistance();
       /* cityInfos.add(new CityInfo("北京西站", 116.32810279404169, 39.90083463437583));
        cityInfos.add(new CityInfo("北京市小园地铁站", 116.11713134332637, 39.89585544013442));
        cityInfos.add(new CityInfo("北京市金安桥地铁站",116.16743224512043 , 39.930961767459297));
        cityInfos.add(new CityInfo("北京市草房地铁站", 116.39812867295258, 39.99261739912267));
        cityInfos.add(new CityInfo("北京市回龙观地铁站", 116.4133836971231, 39.910924547299568));
        cityInfos.add(new CityInfo("北京市天通苑地铁站", 116.42784821244477, 40.07495397605039));
        cityInfos.add(new CityInfo("北京市西红门地铁站", 116.4133836971231, 39.910924547299568));
        cityInfos.add(new CityInfo("北京市大红门地铁站", 116.27445437203589, 40.133277791661459));
        cityInfos.add(new CityInfo("北京市角门西地铁站", 116.3827070131101, 39.85563107559189));
        cityInfos.add(new CityInfo("北京市顺义地铁站", 116.66142426369096, 40.13635076223076));
        cityInfos.add(new CityInfo("北京市军事博物馆地铁站", 116.330207434021, 39.914975754513907));
        cityInfos.add(new CityInfo("北京市新宫地铁站", 116.36671340214936, 39.823488313799639));
        cityInfos.add(new CityInfo("北京市健德门地铁站", 116.4133836971231, 39.910924547299568));
        cityInfos.add(new CityInfo("北京市春天里地铁站", 116.46489199608192, 39.91046643479811));
*/
    }
    
    /**
     * <h2>测试 saveCityInfoToRedis 方法</h2>
     */
    @Test
    public void testSaveCityInfoToRedis() {
        System.out.println(geoService.saveCityInfoToRedis(cityInfos,AppConfig.PROJECT_KEY));
    }

    @Test
    public void test() {
        String city[] = {"北京西站"};
        List<Point> cityPos = geoService.getCityPos(city, AppConfig.PROJECT_KEY);
        System.out.println(cityPos.get(0) == null);
        for (Point cityPo : cityPos) {
            System.out.println(cityPo);
        }
    }

    /**
     * <h2>测试 getCityPos 方法</h2>
     * 如果传递的 city 在 Redis 中没有记录, 会返回什么呢 ? 例如, 这里传递的 xxx
     */
    @Test
    public void testGetCityPos() {

        System.out.println(JSON.toJSONString(geoService.getCityPos(
                Arrays.asList("anqing", "suzhou", "xxx").toArray(new String[3]),AppConfig.PROJECT_KEY
        )));
    }

    /**
     * <h2>测试 getTwoCityDistance 方法</h2>
     */
    @Test
    public void testGetTwoCityDistance() {
        System.out.println(geoService.getTwoCityDistance("北京市光耀东方", "北京西站", null,AppConfig.PROJECT_KEY).getValue());
        System.out.println(geoService.getTwoCityDistance("北京市光耀东方", "北京西站", Metrics.KILOMETERS,AppConfig.PROJECT_KEY).getValue());
        System.out.println(geoService.getTwoCityDistance("北京市光耀东方", "北京市小园地铁站", Metrics.KILOMETERS,AppConfig.PROJECT_KEY).getValue());
    }

    /**
     * <h2>测试 getPointRadius 方法</h2>
     */
    @Test
    public void testGetPointRadius() {
        Point center = new Point(125.3306020759069, 43.82195350104314);
        Distance radius = new Distance(20, Metrics.KILOMETERS);
        Circle within = new Circle(center, radius);
        // order by 距离 limit 2, 同时返回距离中心点的距离
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(2).sortAscending();
        System.out.println(JSON.toJSONString(geoService.getPointRadius(within, args,AppConfig.COMPANY_KEY)));
    }

    /**
     * <h2>测试 getMemberRadius 方法</h2>
     */
    @Test
    public void testGetMemberRadius() {

        Distance radius = new Distance(200, Metrics.KILOMETERS);

        System.out.println(JSON.toJSONString(geoService.getMemberRadius("suzhou", radius, null,AppConfig.PROJECT_KEY)));

        // order by 距离 limit 2, 同时返回距离中心点的距离
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(2).sortAscending();
        System.out.println(JSON.toJSONString(geoService.getMemberRadius("suzhou", radius, args,AppConfig.PROJECT_KEY)));
    }

    /**
     * <h2>测试 getCityGeoHash 方法</h2>
     */
    @Test
    public void testGetCityGeoHash() {
        System.out.println(JSON.toJSONString(geoService.getCityGeoHash(
                Arrays.asList("anqing", "suzhou", "xxx").toArray(new String[3]),AppConfig.PROJECT_KEY
        )));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值