如何在 Java 中利用 redis 实现 LBS 服务
前言
lbs(基于位置的服务) 服务是现在移动互联网中比较常用的功能。例如外卖服务中常用的我附近的店铺的功能,通常是以用户当前的位置坐标为基础,查询一定距离范围类的店铺,按照距离远近进行倒序排序。
自从 redis 4 版本发布后, lbs 相关命令正式内置在 redis 的发行版中。要实现上述的功能,主要用到 redis geo 相关的两个命令
geoadd 和 georadious
命令描述
geoadd
geoadd key longitude latitude member [longitude latitude member ...]
这个命令将指定的地理空间位置(纬度、经度、名称)添加到指定的 key 中。
有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。
当坐标位置超出上述指定范围时,该命令将会返回一个错误。
该命令可以一次添加多个地理位置点
georadious
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
这个命令以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
范围可以使用以下其中一个单位:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
在给定以下可选项时, 命令会返回额外的信息:
- withdist: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
- withcoord: 将位置元素的经度和维度也一并返回。
- withhash: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
- asc: 根据中心的位置, 按照从近到远的方式返回位置元素。
- desc: 根据中心的位置, 按照从远到近的方式返回位置元素。
- 在默认情况下, georadius 命令会返回所有匹配的位置元素。 虽然用户可以使用 count <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服务。
代码同步发布在 github 仓库中
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 与众不同的 Java 日期格式化大全
下一篇: 简单学习Java API 设计实践