如何在 Java 中利用 redis 實(shí)現(xiàn) LBS 服務(wù)
前言
LBS(基于位置的服務(wù)) 服務(wù)是現(xiàn)在移動(dòng)互聯(lián)網(wǎng)中比較常用的功能。例如外賣服務(wù)中常用的我附近的店鋪的功能,通常是以用戶當(dāng)前的位置坐標(biāo)為基礎(chǔ),查詢一定距離范圍類的店鋪,按照距離遠(yuǎn)近進(jìn)行倒序排序。
自從 redis 4 版本發(fā)布后, lbs 相關(guān)命令正式內(nèi)置在 redis 的發(fā)行版中。要實(shí)現(xiàn)上述的功能,主要用到 redis geo 相關(guān)的兩個(gè)命令
GEOADD 和 GEORADIOUS
命令描述
GEOADD
GEOADD key longitude latitude member [longitude latitude member ...]
這個(gè)命令將指定的地理空間位置(緯度、經(jīng)度、名稱)添加到指定的 key 中。
有效的經(jīng)度從-180度到180度。
有效的緯度從-85.05112878度到85.05112878度。
當(dāng)坐標(biāo)位置超出上述指定范圍時(shí),該命令將會(huì)返回一個(gè)錯(cuò)誤。
該命令可以一次添加多個(gè)地理位置點(diǎn)
GEORADIOUS
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
這個(gè)命令以給定的經(jīng)緯度為中心, 返回鍵包含的位置元素當(dāng)中, 與中心的距離不超過(guò)給定最大距離的所有位置元素。
范圍可以使用以下其中一個(gè)單位:
- m 表示單位為米。
- km 表示單位為千米。
- mi 表示單位為英里。
- ft 表示單位為英尺。
在給定以下可選項(xiàng)時(shí), 命令會(huì)返回額外的信息:
- WITHDIST: 在返回位置元素的同時(shí), 將位置元素與中心之間的距離也一并返回。 距離的單位和用戶給定的范圍單位保持一致。
- WITHCOORD: 將位置元素的經(jīng)度和維度也一并返回。
- WITHHASH: 以 52 位有符號(hào)整數(shù)的形式, 返回位置元素經(jīng)過(guò)原始 geohash 編碼的有序集合分值。 這個(gè)選項(xiàng)主要用于底層應(yīng)用或者調(diào)試, 實(shí)際中的作用并不大。
- ASC: 根據(jù)中心的位置, 按照從近到遠(yuǎn)的方式返回位置元素。
- DESC: 根據(jù)中心的位置, 按照從遠(yuǎn)到近的方式返回位置元素。
- 在默認(rèn)情況下, GEORADIUS 命令會(huì)返回所有匹配的位置元素。 雖然用戶可以使用 COUNT <count> 選項(xiàng)去獲取前 N 個(gè)匹配元素
接口定義
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 {
/**
* 存儲(chǔ)一個(gè)位置
*
* @param postion 增加的位置對(duì)象
* @throws Exception
*/
boolean addPostion(Postion postion);
/**
* 查詢以指定的坐標(biāo)為中心,指定的距離為半徑的范圍類的所有位置點(diǎn)
*
* @param center 中心點(diǎn)位置
* @param distinct 最遠(yuǎn)距離,單位米
* @param asc 是否倒序排序
* @return 有效的位置
*/
List<Postion> radious(String type, GeoCoordinate center, Long distinct, Boolean asc);
}
實(shí)現(xiàn)的接口
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;
}
}
測(cè)試用例
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服務(wù)測(cè)試類
*
* @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);
}
}
測(cè)試結(jié)果

LBS 服務(wù)測(cè)試結(jié)果
后記
這樣,我們通過(guò) redis 就能簡(jiǎn)單實(shí)現(xiàn)一個(gè)我附近的小店的功能的 LBS服務(wù)。
代碼同步發(fā)布在 GitHub 倉(cāng)庫(kù)中
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringCloud 服務(wù)注冊(cè)和消費(fèi)實(shí)現(xiàn)過(guò)程
這篇文章主要介紹了SpringCloud 服務(wù)注冊(cè)和消費(fèi)實(shí)現(xiàn)過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java中Lambda表達(dá)式和函數(shù)式接口的使用和特性
Java Lambda表達(dá)式是一種函數(shù)式編程的特性,可簡(jiǎn)化匿名內(nèi)部類的寫法,與函數(shù)式接口搭配使用,實(shí)現(xiàn)代碼簡(jiǎn)潔、可讀性高、易于維護(hù)的特點(diǎn),適用于集合操作、多線程編程等場(chǎng)景2023-04-04
Java判斷IP地址為內(nèi)網(wǎng)IP還是公網(wǎng)IP的方法
這篇文章主要介紹了Java判斷IP地址為內(nèi)網(wǎng)IP還是公網(wǎng)IP的方法,針對(duì)tcp/ip協(xié)議中保留的三個(gè)私有地址進(jìn)行判斷分析,是比較實(shí)用的技巧,需要的朋友可以參考下2015-01-01
spring是如何實(shí)現(xiàn)聲明式事務(wù)的
這篇文章主要介紹了spring是如何實(shí)現(xiàn)聲明式事務(wù)的,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
解決Jackson解析嵌套類問(wèn)題(MismatchedInputException)
這篇文章主要介紹了解決Jackson解析嵌套類問(wèn)題(MismatchedInputException),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06

