最近使用团油的时候总是觉得他的那个按照距离排序的功能很好,所以就试着研究一下。

1. 新建spring boot项目

1.1 pom.xml

添加redis依赖和lombok依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
    <scope>provided</scope>
</dependency>

1.2 application.yml

# Redis数据库索引(默认为0)
spring:
  redis:
    database: 0
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password:
    # 连接池最大连接数(使用负值表示没有限制)
    jedis:
      pool:
        max-active: 20
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0
    # 连接超时时间(毫秒)
    timeout: 1000

1.3 新建实体类

/**
 * 油站实体类
 * @author zhouzhaodong
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ServiceStation implements Serializable {

    /** 油站 */
    private String serviceStationName;

    /** 经度 */
    private Double longitude;

    /** 纬度 */
    private Double latitude;

}

1.4 新建service

/**
 * 服务接口定义
 * @author zhouzhaodong
 */
public interface RedisGeoService {

    /**
     * 把油站信息保存到 Redis 中
     * @param serviceStations {@link ServiceStation}
     * @return 成功保存的个数
     * */
    Long saveServiceStationToRedis(Collection<ServiceStation> serviceStations);

    /**
     * 获取给定油站的坐标
     * @param serviceStations 给定油站 key
     * @return {@link Point}s
     * */
    List<Point> getServiceStationPos(String[] serviceStations);

    /**
     * 获取两个油站之间的距离
     * @param serviceStation1 第一个油站
     * @param serviceStation2 第二个油站
     * @param metric {@link Metric} 单位信息, 可以是 null
     * @return {@link Distance}
     * */
    Distance getTwoServiceStationDistance(String serviceStation1, String serviceStation2, Metric metric);

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

    /**
     * 根据给定地理位置获取指定范围内的地理位置集合
     * @param member 油站名称
     * @param distance 距离范围
     * @param args {@link RedisGeoCommands.GeoRadiusCommandArgs} 限制返回的个数和排序方式, 可以是 null
     * @return
     */
    GeoResults<RedisGeoCommands.GeoLocation<String>> getMemberRadius(
            String member, Distance distance, RedisGeoCommands.GeoRadiusCommandArgs args);

    /**
     * 获取某个地理位置的 geohash 值
     * @param serviceStations 给定油站 key
     * @return city geohashs
     * */
    List<String> getServiceStationGeoHash(String[] serviceStations);

}

1.5 新建service实现类

/**
 * 服务接口实现
 * @author zhouzhaodong
 */
@Service
@Slf4j
public class RedisGeoServiceImpl implements RedisGeoService {

    /**
     * redis的key
     */
    private final String GEO_KEY = "ah-cities";

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public Long saveServiceStationToRedis(Collection<ServiceStation> serviceStation) {
        log.info("start to save station info: {}.", serviceStation);

        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        Set<RedisGeoCommands.GeoLocation<String>> locations = new HashSet<>();
        // 将坐标转为坐标点
        serviceStation.forEach(ci -> locations.add(new RedisGeoCommands.GeoLocation<>(
                ci.getServiceStationName(), new Point(ci.getLongitude(), ci.getLatitude())
        )));
        log.info("done to save station info.");
        return ops.add(GEO_KEY, locations);
    }

    @Override
    public List<Point> getServiceStationPos(String[] serviceStations) {
        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        // 根据油站名称获取油站的坐标
        return ops.position(GEO_KEY, serviceStations);
    }

    @Override
    public Distance getTwoServiceStationDistance(String serviceStations1, String serviceStations2, Metric metric) {
        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        return metric == null ?
                ops.distance(GEO_KEY, serviceStations1, serviceStations2) : ops.distance(GEO_KEY, serviceStations1, serviceStations2, 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> getServiceStationGeoHash(String[] serviceStations) {
        GeoOperations<String, String> ops = redisTemplate.opsForGeo();
        return ops.hash(GEO_KEY, serviceStations);
    }

}

1.6 测试代码

@SpringBootTest
class RedisGeoApplicationTests {


    @Autowired
    private RedisGeoService geoService;

    /**
     * 测试 把油站信息保存到 Redis 中
     * */
    @Test
    public void testSaveServiceStationToRedis() {
        List<ServiceStation> serviceStations = new ArrayList<>();
        serviceStations.add(new ServiceStation("金盾", 117.17, 31.52));
        serviceStations.add(new ServiceStation("中石油", 117.02, 30.31));
        serviceStations.add(new ServiceStation("中石化", 116.47, 33.57));
        serviceStations.add(new ServiceStation("山东石化", 116.58, 33.38));
        serviceStations.add(new ServiceStation("青岛石化", 115.48, 32.54));
        serviceStations.add(new ServiceStation("壳牌", 117.21, 32.56));
        serviceStations.add(new ServiceStation("中国化工", 118.18, 29.43));
        System.out.println(geoService.saveServiceStationToRedis(serviceStations));
    }

    /**
     * 测试 获取给定油站的坐标
     * 如果传递的 city 在 Redis 中没有记录, 会返回什么呢 ? 例如, 这里传递的 xxx
     * */
    @Test
    public void testGetServiceStationPos() {

        System.out.println(geoService.getServiceStationPos(
                Arrays.asList("中石油", "中石化", "xxx").toArray(new String[3])
        ));
    }

    /**
     * 测试 获取两个油站之间的距离
     * */
    @Test
    public void testGetTwoServiceStationDistance() {

        System.out.println(geoService.getTwoServiceStationDistance("壳牌", "金盾", null).getValue());
        System.out.println(geoService.getTwoServiceStationDistance("壳牌", "金盾", Metrics.KILOMETERS).getValue());
    }

    /**
     * 测试 根据给定地理位置坐标获取指定范围内的地理位置集合
     * */
    @Test
    public void testGetPointRadius() {

        Point center = new Point(117.17, 31.52);
        Distance radius = new Distance(140, Metrics.KILOMETERS);
        Circle within = new Circle(center, radius);

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

        // 获取前两个油站位置, 同时返回距离中心点的距离
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(2).sortAscending();
        System.out.println(geoService.getPointRadius(within, args));
    }

    /**
     * 测试 根据给定地理位置获取指定范围内的地理位置集合
     * */
    @Test
    public void testGetMemberRadius() {

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

        System.out.println(geoService.getMemberRadius("金盾", radius, null));

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

    /**
     * 测试 获取某个地理位置的 geohash 值
     * */
    @Test
    public void testGetServiceStationGeoHash() {

        System.out.println(geoService.getServiceStationGeoHash(
                Arrays.asList("中石化", "中石油", "xxx").toArray(new String[3])
        ));
    }
}

完毕!

源代码地址

https://github.com/zhouzhaodong/springboot/tree/master/redis_geo


周兆东
107 声望21 粉丝

一个java小白的成长之路。。。