SpringBoot 使用 Redis Geo

邢闖洋發表於2021-09-10

Redis GEO 的說明

Redis 官方對 Geo 的描述

Redis 3.2 contains significant changes to the API and implementation of Redis. A new set of commands for Geo indexing was added (GEOADD, GEORADIUS and related commands).

關於 Geo,需要知道

  • Redis 的 Geo 是在 3.2 版本才有的
  • 使用 geohash 儲存地理位置的座標
  • 使用有序集合(zset)儲存地理位置的集合

6個操作命令

  • GEOADD:增加某個地理位置的座標
  • GEOPOS:獲取某個地理位置的座標
  • GEODIST:獲取兩個地理位置的距離
  • GEORADIUS:根據給定地理位置座標獲取指定範圍內的地理位置集合
  • GEORADIUSBYMEMBER:根據給定地理位置獲取指定範圍內的地理位置集合
  • GEOHASH:獲取某個地理位置的 geohash 值

命令的使用方法

SpringBoot 使用 Redis Geo(例項)

說明:以城市資訊為目標,使用 StringRedisTemplate 操作 Redis 提供的關於 Geo 的6個命令。

  • JDK 版本
<properties>
        <java.version>1.8</java.version>
</properties>
  • SpringBoot 版本
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/>
</parent>
  • redis starter
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • vo 物件定義
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * <h1>城市資訊</h1>
 * Created by Qinyi.
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CityInfo {

    /** 城市 */
    private String city;

    /** 經度 */
    private Double longitude;

    /** 緯度 */
    private Double latitude;
}
  • 服務介面定義
import com.imooc.ad.vo.CityInfo;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;

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

/**
 * <h1>Geo 服務介面定義</h1>
 * Created by Qinyi.
 */
public interface IGeoService {

    /**
     * <h2>把城市資訊儲存到 Redis 中</h2>
     * @param cityInfos {@link CityInfo}
     * @return 成功儲存的個數
     * */
    Long saveCityInfoToRedis(Collection<CityInfo> cityInfos);

    /**
     * <h2>獲取給定城市的座標</h2>
     * @param cities 給定城市 key
     * @return {@link Point}s
     * */
    List<Point> getCityPos(String[] cities);

    /**
     * <h2>獲取兩個城市之間的距離</h2>
     * @param city1 第一個城市
     * @param city2 第二個城市
     * @param metric {@link Metric} 單位資訊, 可以是 null
     * @return {@link Distance}
     * */
    Distance getTwoCityDistance(String city1, String city2, Metric metric);

    /**
     * <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);

    /**
     * <h2>根據給定地理位置獲取指定範圍內的地理位置集合</h2>
     * */
    GeoResults<RedisGeoCommands.GeoLocation<String>> getMemberRadius(
            String member, Distance distance, RedisGeoCommands.GeoRadiusCommandArgs args);

    /**
     * <h2>獲取某個地理位置的 geohash 值</h2>
     * @param cities 給定城市 key
     * @return city geohashs
     * */
    List<String> getCityGeoHash(String[] cities);
}
  • 服務介面實現
import com.alibaba.fastjson.JSON;
import com.imooc.ad.service.IGeoService;
import com.imooc.ad.vo.CityInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Point;
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;

/**
 * <h1>Geo 服務介面實現</h1>
 * Created by Qinyi.
 */
@Slf4j
@Service
public class GeoServiceImpl implements IGeoService {

    private final String GEO_KEY = "ah-cities";

    /** redis 客戶端 */
    private final StringRedisTemplate redisTemplate;

    @Autowired
    public GeoServiceImpl(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public Long saveCityInfoToRedis(Collection<CityInfo> cityInfos) {

        log.info("start to save city info: {}.", JSON.toJSONString(cityInfos));

        GeoOperations<String, String> ops = redisTemplate.opsForGeo();

        Set<RedisGeoCommands.GeoLocation<String>> locations = new HashSet<>();
        cityInfos.forEach(ci -> locations.add(new RedisGeoCommands.GeoLocation<String>(
                ci.getCity(), new Point(ci.getLongitude(), ci.getLatitude())
        )));

        log.info("done to save city info.");

        return ops.add(GEO_KEY, locations);
    }

    @Override
    public List<Point> getCityPos(String[] cities) {

        GeoOperations<String, String> ops = redisTemplate.opsForGeo();

        return ops.position(GEO_KEY, cities);
    }

    @Override
    public Distance getTwoCityDistance(String city1, String city2, Metric metric) {

        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
    ) {

        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
    ) {

        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) {

        GeoOperations<String, String> ops = redisTemplate.opsForGeo();

        return ops.hash(GEO_KEY, cities);
    }
}
  • 測試用例
import com.alibaba.fastjson.JSON;
import com.imooc.ad.Application;
import com.imooc.ad.vo.CityInfo;
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;

/**
 * <h1>GeoService 測試用例</h1>
 * Created by Qinyi.
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class GeoServiceTest {

    /** fake some cityInfos */
    private List<CityInfo> cityInfos;

    @Autowired
    private IGeoService geoService;

    @Before
    public void init() {

        cityInfos = new ArrayList<>();

        cityInfos.add(new CityInfo("hefei", 117.17, 31.52));
        cityInfos.add(new CityInfo("anqing", 117.02, 30.31));
        cityInfos.add(new CityInfo("huaibei", 116.47, 33.57));
        cityInfos.add(new CityInfo("suzhou", 116.58, 33.38));
        cityInfos.add(new CityInfo("fuyang", 115.48, 32.54));
        cityInfos.add(new CityInfo("bengbu", 117.21, 32.56));
        cityInfos.add(new CityInfo("huangshan", 118.18, 29.43));
    }

    /**
     * <h2>測試 saveCityInfoToRedis 方法</h2>
     * */
    @Test
    public void testSaveCityInfoToRedis() {

        System.out.println(geoService.saveCityInfoToRedis(cityInfos));
    }

    /**
     * <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])
        )));
    }

    /**
     * <h2>測試 getTwoCityDistance 方法</h2>
     * */
    @Test
    public void testGetTwoCityDistance() {

        System.out.println(geoService.getTwoCityDistance("anqing", "suzhou", null).getValue());
        System.out.println(geoService.getTwoCityDistance("anqing", "suzhou", Metrics.KILOMETERS).getValue());
    }

    /**
     * <h2>測試 getPointRadius 方法</h2>
     * */
    @Test
    public void testGetPointRadius() {

        Point center = new Point(cityInfos.get(0).getLongitude(), cityInfos.get(0).getLatitude());
        Distance radius = new Distance(200, Metrics.KILOMETERS);
        Circle within = new Circle(center, radius);

        System.out.println(JSON.toJSONString(geoService.getPointRadius(within, null)));

        // order by 距離 limit 2, 同時返回距離中心點的距離
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(2).sortAscending();
        System.out.println(JSON.toJSONString(geoService.getPointRadius(within, args)));
    }

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

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

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

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

    /**
     * <h2>測試 getCityGeoHash 方法</h2>
     * */
    @Test
    public void testGetCityGeoHash() {

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

作者:張勤一
連結:www.imooc.com/article/280622?block_...
來源:慕課網
本文原創釋出於慕課網 ,轉載請註明出處,謝謝合作

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章