基于Redis GEO(地理位置) 实现附近的人,商家等相关功能实现 使用SpringBoot Redis工具类

1、基本介绍

1、Redis GEO

主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。

2、基础语法

GEOADD

将给定的空间元素(纬度、经度、名字)添加到指定的键里面。

  • 有效的经度介于 -180 度至 180 度之间。
  • 有效的纬度介于 -85.05112878 度至 85.05112878 度之间。

超过会报org.springframework.data.redis.RedisSystemException异常
示例:

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

redis> GEODIST Sicily Palermo Catania
"166274.15156960039"

redis> GEORADIUS Sicily 15 37 100 km
1) "Catania"

redis> GEORADIUS Sicily 15 37 200 km
1) "Palermo"
2) "Catania"

返回值: 新添加到键里面的空间元素数量, 不包括那些已经存在但是被更新的元素。

GEOPOS

从键里面返回所有给定位置元素的位置(经度和纬度)。
示例:

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

redis> GEOPOS Sicily Palermo Catania NonExisting
1) 1) "13.361389338970184"
   2) "38.115556395496299"
2) 1) "15.087267458438873"
   2) "37.50266842333162"
3) (nil)

返回值:
命令返回一个数组, 数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度。 当给定的位置元素不存在时, 对应的数组项为空值。

GEODIST

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

  • m 表示单位为米。
  • km表示单位为千米。
  • mi表示单位为英里。
  • ft表示单位为英尺。后的工具类没有这个单位
    示例:
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

redis> GEODIST Sicily Palermo Catania
"166274.15156960039"

redis> GEODIST Sicily Palermo Catania km
"166.27415156960038"

redis> GEODIST Sicily Palermo Catania mi
"103.31822459492736"

redis> GEODIST Sicily Foo Bar
(nil)

返回值:
计算出的距离会以双精度浮点数的形式被返回。 如果给定的位置元素不存在, 那么命令返回空值。

GEORADIUS

以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

范围可以使用以下其中一个单位:

  • m 表示单位为米。
  • km表示单位为千米。
  • mi表示单位为英里。
  • ft表示单位为英尺。后的工具类没有这个单位

示例:

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

redis> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
   2) "190.4424"
2) 1) "Catania"
   2) "56.4413"

redis> GEORADIUS Sicily 15 37 200 km WITHCOORD
1) 1) "Palermo"
   2) 1) "13.361389338970184"
      2) "38.115556395496299"
2) 1) "Catania"
   2) 1) "15.087267458438873"
      2) "37.50266842333162"

redis> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
   2) "190.4424"
   3) 1) "13.361389338970184"
      2) "38.115556395496299"
2) 1) "Catania"
   2) "56.4413"
   3) 1) "15.087267458438873"
      2) "37.50266842333162"

GEOHASH

返回一个或多个位置元素的Geohash 表示。
示例:

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

redis> GEOHASH Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"

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

2、可用于实现的功能

  • 微信(其他App)中附近的人
  • 附近的商家(店铺)
  • 等用于查询附近的位置功能

3、SpringBoot实现

1、需要的依赖(使用JDK 1.8)

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

2、编写配置类(解决Redis乱码)

package top.ddandang.nearby.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;

/**
 * <p>
 * Redis配置文件
 * </p>
 *
 * @author D
 * @version 1.0
 * @date 2020/6/6 14:13
 */
@Configuration
public class RedisConfig {
    
    

    private Jackson2JsonRedisSerializer<Object> getJackson2JsonRedisSerializer(){
    
    
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        return jackson2JsonRedisSerializer;
    }

    @Bean(name = "redisTemplate")
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    
    
        // 我们为了自己开发方便,一般直接使用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);

        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = getJackson2JsonRedisSerializer();
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }


    private Duration timeToLive = Duration.ZERO;
    public void setTimeToLive(Duration timeToLive) {
    
    
        this.timeToLive = timeToLive;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
    
    
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = getJackson2JsonRedisSerializer();

        // 配置序列化(解决乱码的问题)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(timeToLive)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();

        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
    }

}

3、编写Redis GEO 工具类

该工具类只包含GEO基本操作,对于其他的数据类型可以参考
Redis工具类

有些地方没有做参数的检测,可能有些异常需要自行进行完善下。

package top.ddandang.nearby.utils;

import org.springframework.data.geo.*;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *
 * </p>
 *
 * @author D
 * @version 1.0
 * @date 2020/6/6 14:18
 */
@Component
public class RedisUtil {
    
    
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 一个个增加地理位置
     *
     * @param key       键值
     * @param longitude 经度
     * @param latitude  纬度
     * @param member    成员
     * @return 返回添加的行数 为 -1 则添加失败
     */
    public long geoAdd(String key, Double longitude, Double latitude, Object member) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        if (member == null || "".equals(member)) {
    
    
            throw new RuntimeException("成员不能为空!");
        }
        if (longitude == null || latitude == null) {
    
    
            throw new RuntimeException("经纬度不能为空!");
        }

        try {
    
    
            Point point = new Point(longitude, latitude);
            System.out.println(point);
            Long l = redisTemplate.opsForGeo().add(key, point, member);
            if (l == null) {
    
    
                return -1;
            } else {
    
    
                return l;
            }
        } catch (RedisSystemException e) {
    
    
            System.out.println("经纬度有误!");
            e.printStackTrace();
            return -1;
        }
    }

    /**
     * 一个个增加地理位置
     *
     * @param key         键值
     * @param geoLocation 存放成员 member 和 经纬度的 point
     * @return 返回添加的行数 为 -1 则添加失败
     */
    public long geoAdd(String key, RedisGeoCommands.GeoLocation<Object> geoLocation) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }

        try {
    
    
            Long l = redisTemplate.opsForGeo().add(key, geoLocation);
            if (l == null) {
    
    
                return -1;
            } else {
    
    
                return l;
            }
        } catch (RedisSystemException e) {
    
    
            System.out.println("经纬度有误!");
            e.printStackTrace();
            return -1;
        }
    }

    /**
     * 一次性添加多个位置信息
     *
     * @param key                 键值
     * @param memberCoordinateMap 存放位置信息
     * @return 添加的行数 -1 为全部都添加失败
     */
    public long geoAdd(String key, Map<Object, Point> memberCoordinateMap) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        try {
    
    
            Long l = redisTemplate.opsForGeo().add(key, memberCoordinateMap);
            if (l == null) {
    
    
                return -1;
            } else {
    
    
                return l;
            }
        } catch (RedisSystemException e) {
    
    
            System.out.println("经纬度有误!");
            e.printStackTrace();
            return -1;
        }
    }

    /**
     * 获取两个地点之间的距离
     *
     * @param key     键值
     * @param member1 成员 1
     * @param member2 成员 2
     * @return 两个之间的距离, 单位为米(M/m) 如果成员不存在则返回{@literal null}
     */
    public Distance geoDistance(String key, Object member1, Object member2) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        try {
    
    
            return redisTemplate.opsForGeo().distance(key, member1, member2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取两个地点之间的距离
     *
     * @param key     键值
     * @param member1 成员 1
     * @param member2 成员 2
     * @param metrics 指定单位(默认为米) 千米(km)  英里(mi)
     * @return 两个之间的距离, 单位为米(M/m) 如果成员不存在则返回{@literal null}
     */
    public Distance geoDistance(String key, Object member1, Object member2, Metrics metrics) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        try {
    
    
            return redisTemplate.opsForGeo().distance(key, member1, member2, metrics);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 返回geohash geohash:基本原理是将地球理解为一个二维平面,将平面递归分解成更小的子块,
     * 每个子块在一定经纬度范围内拥有相同的编码
     *
     * @param key     键值
     * @param members 成员值
     * @return 返回成员对应的geohash 如果成员不存在则geohash为null
     */
    public List<String> geoHash(String key, Object... members) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        return redisTemplate.opsForGeo().hash(key, members);
    }

    /**
     * 返回地点的经纬度
     *
     * @param key     键值
     * @param members 成员值
     * @return 返回成员对应的Point(经纬度) 如果成员不存在则为null
     */
    public List<Point> geoPosition(String key, Object... members) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        return redisTemplate.opsForGeo().position(key, members);
    }

    /**
     * 返回指定点 指定范围内存在的成员
     *
     * @param key    键值
     * @param within Circle 对象 Point(center) 中心点经纬度,Distance(radius)查找的范围
     * @return GeoResults对象
     */
    public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoRadius(String key, Circle within) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        return redisTemplate.opsForGeo().radius(key, within);
    }

    /**
     * 返回指定点 指定范围内存在的成员,并指定最多显示多少个成员
     *
     * @param key    键值
     * @param within Circle 对象 Point(center) 中心点经纬度,Distance(radius)查找的范围
     * @param args   includeCoordinates() 返回成员的坐标 includeDistance() 返回距离中心点的距离 同时可以设置最多显示的个数 和升序还是降序排列
     * @return GeoResults对象 可以使用GeoRadiusCommandArgs对象进行设计显示坐标和距离
     */
    public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoRadius(String key, Circle within, RedisGeoCommands.GeoRadiusCommandArgs args) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        return redisTemplate.opsForGeo().radius(key, within, args);
    }


    /**
     * 返回改成员指定范围内中存在的其他成员 包含自身
     *
     * @param key    键值
     * @param member 成员
     * @param radius 距离 默认米
     * @return GeoResults对象
     */
    public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoRadius(String key, Object member, double radius) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        return redisTemplate.opsForGeo().radius(key, member, radius);
    }

    /**
     * 返回改成员指定范围内中存在的其他成员 包含自身
     *
     * @param key      键值
     * @param member   成员
     * @param distance 距离和指定单位
     * @return GeoResults对象 包含自身
     */
    public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoRadius(String key, Object member, Distance distance) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        return redisTemplate.opsForGeo().radius(key, member, distance);
    }

    /**
     * 返回改成员指定范围内中存在的其他成员 包含自身
     *
     * @param key      键值
     * @param member   成员
     * @param distance 距离和指定单位
     * @param args   includeCoordinates() 返回成员的坐标 includeDistance() 返回距离中心点的距离 同时可以设置最多显示的个数 和升序还是降序排列
     * @return GeoResults对象 包含自身
     */
    public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoRadius(String key, Object member, Distance distance, RedisGeoCommands.GeoRadiusCommandArgs args) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        return redisTemplate.opsForGeo().radius(key, member, distance, args);
    }

    /**
     * 删除成员
     * @param key 键值
     * @param members 成员 可变参数
     * @return 返回删除的个数 -1为删除失败
     */
    public long geoRemove(String key, Object... members) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        Long remove = redisTemplate.opsForGeo().remove(key, members);
        if (remove == null) {
    
    
            return -1;
        } else {
    
    
            return remove;
        }
    }
}

4、基本测试

geoAdd方法

注意: 有效的经度介于 -180 度至 180 度之间。有效的纬度介于 -85.05112878 度至 85.05112878 度之间。

测试方法

    @Test
    void testGeoAdd() {
    
    
        System.out.println("直接传递经纬度和名字");
        //直接传递经纬度和名字
        long geoAdd = redisUtil.geoAdd("geo", 113.3941210075202, 22.50311850818019, "中山孙文纪念公园");
        System.out.println(geoAdd);

        System.out.println("传递RedisGeoCommands.GeoLocation对象");
        //传递RedisGeoCommands.GeoLocation对象
        RedisGeoCommands.GeoLocation<Object> geoLocation = new RedisGeoCommands.GeoLocation<>("中山市中医院", new Point(113.36044234704323, 22.54405895090185));
        geoAdd = redisUtil.geoAdd("geo", geoLocation);
        System.out.println(geoAdd);

        //上面都是一个个的增加
        //使用Map可以进行批量的增加
        System.out.println("使用Map可以进行批量的增加");
        Map<Object, Point> memberCoordinateMap = new HashMap<>();
        memberCoordinateMap.put("中山北站", new Point(113.3893593296697, 22.56292564924235));
        memberCoordinateMap.put("广州白云机场", new Point(113.31528491501277, 23.392348662476326));
        memberCoordinateMap.put("深圳宝安国际机场", new Point(113.823079157541, 22.637003705284336));
        geoAdd = redisUtil.geoAdd("geo", memberCoordinateMap);
        System.out.println(geoAdd);
    }

测试输出
在这里插入图片描述
查看Redis中数据(GEO是使用有序集合进行存放的)
在这里插入图片描述

geoDistance方法

默认返回的范围是米,但是可以进行指定单位返回(千米,英里)。

注意: 输入不存在的成员时会报空指针异常

测试方法

 @Test
    void geoDistance() {
    
    

        //默认单位是米,如果存在
        Distance distance = redisUtil.geoDistance("geo", "中山北站", "广州白云机场");
        if (distance == null) {
    
    
            System.out.println("空指针");
        } else {
    
    
            System.out.print("中山北站距离广州白云机场");
            System.out.println(distance);
        }

        //输入不存在的成员时会报空指针异常
        distance = redisUtil.geoDistance("geo", "不存在", "广州白云机场");
        if (distance == null) {
    
    
            System.out.println("空指针");
        } else {
    
    
            System.out.println(distance);
        }

        distance = redisUtil.geoDistance("geo", "深圳宝安国际机场", "中山孙文纪念公园", Metrics.KILOMETERS);

        if (distance == null) {
    
    
            System.out.println("空指针");
        } else {
    
    
            System.out.print("深圳宝安国际机场距离中山孙文纪念公园");
            System.out.println(distance);
        }
    }

输出结果
在这里插入图片描述

geoHash方法

返回geohash。不存在的成员会返回Null

geohash: 基本原理是将地球理解为一个二维平面,将平面递归分解成更小的子块。

测试方法:

    @Test
    void geoHash() {
    
    
        List<String> list = redisUtil.geoHash("geo", "中山孙文纪念公园", "中山市中医院", "中山北站", "深圳宝安国际机场", "不存在");
        list.forEach(System.out::println);
    }

输出结果:
在这里插入图片描述
geohash 在一定的区域有共同的编码,比如中山孙文纪念公园中山市中医院的geohash编码在前面的四位相同。

geoPosition方法

返回成员的经纬度(Point对象),不存在的成员同样返回Null。

测试方法:

    @Test
    void geoPosition() {
    
    
        List<Point> list = redisUtil.geoPosition("geo", "中山孙文纪念公园", "中山市中医院", "中山北站", "深圳宝安国际机场", "不存在");
        list.forEach(System.out::println);
    }

输出结果:
在这里插入图片描述

geoRadius方法

注意:这里指定某个点的时候如果点不是规定的经纬度会抛出异常
注意:这里以某个成员为中心的时候如果成员不存在也会抛出异常

重载方法一(以某个点为中心点)

GeoResults<RedisGeoCommands.GeoLocation<Object>> geoRadius(String key, Circle within)
Circle对象:包含了Point(经度,纬度)Distance(范围,单位)

测试:
这里以中山北站为中心,查找范围10km之内的成员。(此时不会返回平均距离,和成员的经纬度)

        Circle circle = new Circle(new Point(113.3893593296697, 22.56292564924235), new Distance(10, Metrics.KILOMETERS));
        GeoResults<RedisGeoCommands.GeoLocation<Object>> testGeo = redisUtil.geoRadius("geo", circle);
        System.out.println(testGeo);
        List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = testGeo.getContent();
        content.forEach(System.out::println);

输出
averageDistance: 为平均距离。
RedisGeoCommands.GeoLocation: 为该点的信息,此时不会显示经纬度和距离中心点的距离。

GeoResults: [averageDistance: 0.0, results: GeoResult [content: RedisGeoCommands.GeoLocation(name=中山市中医院, point=null), distance: 0.0, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=中山孙文纪念公园, point=null), distance: 0.0, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=中山北站, point=null), distance: 0.0, ]]
GeoResult [content: RedisGeoCommands.GeoLocation(name=中山市中医院, point=null), distance: 0.0, ]
GeoResult [content: RedisGeoCommands.GeoLocation(name=中山孙文纪念公园, point=null), distance: 0.0, ]
GeoResult [content: RedisGeoCommands.GeoLocation(name=中山北站, point=null), distance: 0.0, ]

重载方法二(以某个点为中心点)

GeoResults<RedisGeoCommands.GeoLocation<Object>> geoRadius(String key, Circle within, RedisGeoCommands.GeoRadiusCommandArgs args)
Circle对象:包含了Point(经度,纬度)Distance(范围,单位)

RedisGeoCommands.GeoRadiusCommandArgs:

  • limit可以设置最多显示的成员
  • sortAscending升序,sortDescending降序。对成员以与中心点的距离进行升序或降序排序。
  • includeDistance可以返回成员距离中心点的距离
  • includeCoordinates 可以返回成员的坐标(经纬度)

测试

    @Test
    void geoRadius() {
    
    
        System.out.println("没有坐标和距离的方法");
        Circle circle = new Circle(new Point(113.3893593296697, 22.56292564924235), new Distance(10, Metrics.KILOMETERS));
        GeoResults<RedisGeoCommands.GeoLocation<Object>> testGeo = redisUtil.geoRadius("geo", circle);
        System.out.println(testGeo);
        List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = testGeo.getContent();
        content.forEach(System.out::println);
        content.clear();


        System.out.println("包含坐标和距离的方法,并进行排序");
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                //设置最多显示两个成员
                .limit(2)
                //按距离进行降序排列
                .sortDescending()
                //包含成员的坐标信息
                .includeCoordinates()
                //包含成员距离中心点的距离
                .includeDistance();

        testGeo = redisUtil.geoRadius("geo", circle, args);
        System.out.println(testGeo);
        content = testGeo.getContent();
        content.forEach(System.out::println);
    }

输出结果

可以看出使用了RedisGeoCommands.GeoRadiusCommandArgs可以显示成员的信息,并可以控制输出量和进行排序。

//没有坐标和距离的方法
GeoResults: [averageDistance: 0.0, results: GeoResult [content: RedisGeoCommands.GeoLocation(name=中山市中医院, point=null), distance: 0.0, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=中山孙文纪念公园, point=null), distance: 0.0, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=中山北站, point=null), distance: 0.0, ]]
GeoResult [content: RedisGeoCommands.GeoLocation(name=中山市中医院, point=null), distance: 0.0, ]
GeoResult [content: RedisGeoCommands.GeoLocation(name=中山孙文纪念公园, point=null), distance: 0.0, ]
GeoResult [content: RedisGeoCommands.GeoLocation(name=中山北站, point=null), distance: 0.0, ]
//包含坐标和距离的方法,并进行排序
GeoResults: [averageDistance: 5.15335 KILOMETERS, results: GeoResult [content: RedisGeoCommands.GeoLocation(name=中山孙文纪念公园, point=Point [x=113.394122, y=22.503119]), distance: 6.6701 KILOMETERS, ],GeoResult [content: RedisGeoCommands.GeoLocation(name=中山市中医院, point=Point [x=113.360445, y=22.544060]), distance: 3.6366 KILOMETERS, ]]
GeoResult [content: RedisGeoCommands.GeoLocation(name=中山孙文纪念公园, point=Point [x=113.394122, y=22.503119]), distance: 6.6701 KILOMETERS, ]
GeoResult [content: RedisGeoCommands.GeoLocation(name=中山市中医院, point=Point [x=113.360445, y=22.544060]), distance: 3.6366 KILOMETERS, ]

重载方法二(以某个成员为中心点)

这里以集合中的一个成员作为中心点,去寻找指定距离内的成员。

这里有三个重载的方法。

  1. geoRadius(String key, Object member, double radius),这里的单位默认为米。
  2. geoRadius(String key, Object member, Distance distance),这里可以使用Distance对象进行指定单位和距离。
  3. geoRadius(String key, Object member, Distance distance, RedisGeoCommands.GeoRadiusCommandArgs args),之类可以使用RedisGeoCommands.GeoRadiusCommandArgs进行排序和显示坐标和距离信息。

geoRemove方法

移除指定键值中的成员信息,因为GEO使用的是有序集合所以底层还是使用ZREM进行移除的。

返回值: 返回删除成功的成员个数,删除失败返回-1。

    /**
     * 删除成员
     * @param key 键值
     * @param members 成员 可变参数
     * @return 返回删除的个数 -1为删除失败
     */
    public long geoRemove(String key, Object... members) {
    
    
        if (key == null || "".equals(key)) {
    
    
            throw new RuntimeException("key值不能为空!");
        }
        Long remove = redisTemplate.opsForGeo().remove(key, members);
        if (remove == null) {
    
    
            return -1;
        } else {
    
    
            return remove;
        }
    }

5、功能实现思路(App附近的人,附近的店铺)

怎样获取?

只需要在用户点击获取(授权)自己本身的位置信息,然后以自身的位置信息为中心进行查找附近的人(店铺)就好了。

是否需要查看详细信息?

设计附近的人,如果只需要显示人名(网名)而不需要查看详细的信息的话,那么Redis GEO成员值设置为人名(网名)即可。如果需要查看详细的信息则成员值需要设计为用户的Id,然后再根据Id查看用户的详细信息(附近的店铺这个功能是需要存Id的,毕竟店铺坐标几乎不会变)。查询用户信息的时候也可以先对用户的信息进行缓存,这样效率高点。

用户可以开启不显示位置

对于这个功能可以直接在用户不显示位置的时候将该用户的位置信息从GEO中直接移除。
后续又需要加入的话再重新获取用户的定位信息进行添加就好了。

如果需要在地图上显示位置

如果需要在地图上显示位置的话,只需要用包含RedisGeoCommands.GeoRadiusCommandArgs的重载方法,这样会返回获取的成员的位置信息(坐标和距离),将获取的位置信息返回到前端进行相应的处理就好了(使用地图Api用返回的坐标进行显示)。

暂时想到这么多了,有补充的可以评论区交流

Redis GEO 的基本语法来自以下链接,其余均原创和查看源码1


  1. http://redisdoc.com/index.html ↩︎

猜你喜欢

转载自blog.csdn.net/d875708765/article/details/108541911
今日推荐