在 Java 中利用 redis 實現 LBS 服務

前言

LBS(基於位置的服務) 服務是如今移動互聯網中比較經常使用的功能。例如外賣服務中經常使用的我附近的店鋪的功能,一般是以用戶當前的位置座標爲基礎,查詢必定距離範圍類的店鋪,按照距離遠近進行倒序排序。java

自從 redis 4 版本發佈後, lbs 相關命令正式內置在 redis 的發行版中。要實現上述的功能,主要用到 redis geo 相關的兩個命令 GEOADD 和 GEORADIOUSgit

命令描述

  1. GEOADD GEOADD key longitude latitude member [longitude latitude member ...] 這個命令將指定的地理空間位置(緯度、經度、名稱)添加到指定的 key 中。 有效的經度從-180度到180度。 有效的緯度從-85.05112878度到85.05112878度。 當座標位置超出上述指定範圍時,該命令將會返回一個錯誤。 該命令能夠一次添加多個地理位置點github

  2. GEORADIOUSredis

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]dom

這個命令以給定的經緯度爲中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的全部位置元素。 範圍能夠使用如下其中一個單位:post

  • m 表示單位爲米。
  • km 表示單位爲公里。
  • mi 表示單位爲英里。
  • ft 表示單位爲英尺。

在給定如下可選項時, 命令會返回額外的信息:測試

  • WITHDIST: 在返回位置元素的同時, 將位置元素與中心之間的距離也一併返回。 距離的單位和用戶給定的範圍單位保持一致。
  • WITHCOORD: 將位置元素的經度和維度也一併返回。
  • WITHHASH: 以 52 位有符號整數的形式, 返回位置元素通過原始 geohash 編碼的有序集合分值。 這個選項主要用於底層應用或者調試, 實際中的做用並不大。
  • ASC: 根據中心的位置, 按照從近到遠的方式返回位置元素。
  • DESC: 根據中心的位置, 按照從遠到近的方式返回位置元素。
  • 在默認狀況下, GEORADIUS 命令會返回全部匹配的位置元素。 雖然用戶能夠使用 COUNT 選項去獲取前 N 個匹配元素

接口定義

package com.x9710.common.redis;

import com.x9710.common.redis.domain.GeoCoordinate;
import com.x9710.common.redis.domain.Postion;

import java.util.List;

public interface LBSService {

/**
 * 存儲一個位置
 *
 * @param postion 增長的位置對象
 * @throws Exception
 */
boolean addPostion(Postion postion);

/**
 * 查詢以指定的座標爲中心,指定的距離爲半徑的範圍類的全部位置點
 *
 * @param center   中心點位置
 * @param distinct 最遠距離,單位米
 * @param asc      是否倒序排序
 * @return 有效的位置
 */
List<Postion> radious(String type, GeoCoordinate center, Long distinct, Boolean asc);
}
複製代碼

實現的接口

package com.x9710.common.redis.impl;

import com.x9710.common.redis.LBSService;
import com.x9710.common.redis.RedisConnection;
import com.x9710.common.redis.domain.GeoCoordinate;
import com.x9710.common.redis.domain.Postion;
import redis.clients.jedis.GeoRadiusResponse;
import redis.clients.jedis.GeoUnit;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.geo.GeoRadiusParam;

import java.util.ArrayList;
import java.util.List;

public class LBSServiceRedisImpl implements LBSService {
private RedisConnection redisConnection;

private Integer dbIndex;

public void setRedisConnection(RedisConnection redisConnection) {
    this.redisConnection = redisConnection;
}

public void setDbIndex(Integer dbIndex) {
    this.dbIndex = dbIndex;
}

public boolean addPostion(Postion postion) {
    Jedis jedis = redisConnection.getJedis();
    try {
        return (1L == jedis.geoadd(postion.getType(),
                postion.getCoordinate().getLongitude(),
                postion.getCoordinate().getLatitude(),
                postion.getId()));
    } finally {
        if (jedis != null) {
            jedis.close();
        }
    }
}

public List<Postion> radious(String type, GeoCoordinate center, Long distinct, Boolean asc) {
    List<Postion> postions = new ArrayList<Postion>();
    Jedis jedis = redisConnection.getJedis();
    try {
        GeoRadiusParam geoRadiusParam = GeoRadiusParam.geoRadiusParam().withCoord().withDist();
        if (asc) {
            geoRadiusParam.sortAscending();
        } else {
            geoRadiusParam.sortDescending();
        }
        List<GeoRadiusResponse> responses = jedis.georadius(type,
                center.getLongitude(),
                center.getLatitude(),
                distinct.doubleValue(),
                GeoUnit.M,
                geoRadiusParam);
        if (responses != null) {
            for (GeoRadiusResponse response : responses) {
                Postion postion = new Postion(response.getMemberByString(),
                        type,
                        response.getCoordinate().getLongitude(),
                        response.getCoordinate().getLatitude());
                postion.setDistinct(response.getDistance());
                postions.add(postion);
            }
        }
    } finally {
        if (jedis != null) {
            jedis.close();
        }
    }

    return postions;
}
}
複製代碼

測試用例

package com.x9710.common.redis.test;

import com.x9710.common.redis.RedisConnection;
import com.x9710.common.redis.domain.GeoCoordinate;
import com.x9710.common.redis.domain.Postion;
import com.x9710.common.redis.impl.CacheServiceRedisImpl;
import com.x9710.common.redis.impl.LBSServiceRedisImpl;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

/**
 * LBS服務測試類
 *
 * @author 楊高超
 * @since 2017-12-28
 */
public class RedisLBSTest {
private CacheServiceRedisImpl cacheService;
private LBSServiceRedisImpl lbsServiceRedis;
private String type = "SHOP";
private GeoCoordinate center;

@Before
public void before() {
    RedisConnection redisConnection = RedisConnectionUtil.create();
    lbsServiceRedis = new LBSServiceRedisImpl();
    lbsServiceRedis.setDbIndex(15);
    lbsServiceRedis.setRedisConnection(redisConnection);
    Postion postion = new Postion("2017122801", type, 91.118970, 29.654210);
    lbsServiceRedis.addPostion(postion);
    postion = new Postion("2017122802", type, 116.373472, 39.972528);
    lbsServiceRedis.addPostion(postion);
    postion = new Postion("2017122803", type, 116.344820, 39.948420);
    lbsServiceRedis.addPostion(postion);
    postion = new Postion("2017122804", type, 116.637920, 39.905460);
    lbsServiceRedis.addPostion(postion);
    postion = new Postion("2017122805", type, 118.514590, 37.448150);
    lbsServiceRedis.addPostion(postion);
    postion = new Postion("2017122806", type, 116.374766, 40.109508);
    lbsServiceRedis.addPostion(postion);
    center = new GeoCoordinate();
    center.setLongitude(116.373472);
    center.setLatitude(39.972528);
}

@Test
public void test10KMRadious() {
    List<Postion> postions = lbsServiceRedis.radious(type, center, 1000 * 10L, true);
    Assert.assertTrue(postions.size() == 2 && exist(postions, "2017122802") && exist(postions, "2017122803"));
}

@Test
public void test50KMRadious() {
    List<Postion> postions = lbsServiceRedis.radious(type, center, 1000 * 50L, true);
    Assert.assertTrue(postions.size() == 4
            && exist(postions, "2017122802")
            && exist(postions, "2017122803")
            && exist(postions, "2017122806")
            && exist(postions, "2017122804"));
}

private boolean exist(List<Postion> postions, String key) {
    if (postions != null) {
        for (Postion postion : postions) {
            if (postion.getId().equals(key)) {
                return true;
            }
        }
    }
    return false;
}

@Before
public void after() {
    RedisConnection redisConnection = RedisConnectionUtil.create();
    cacheService = new CacheServiceRedisImpl();
    cacheService.setDbIndex(15);
    cacheService.setRedisConnection(redisConnection);
    cacheService.delObject(type);
}
}
複製代碼

測試結果

LBS 服務測試結果

後記

這樣,咱們經過 redis 就能簡單實現一個我附近的小店的功能的 LBS服務。this

本文程序是在前面的文章《在 Java 中使用 redis》的基礎上添加新的實現類的方式完成的。代碼同步發佈在 GitHub 倉庫編碼

原文發表在簡書中,原始連接spa

相關文章
相關標籤/搜索