前幾天收到一個新的需求,需要實作類似“附近的人”的功能:根據自己當前的定位,獲取距離范圍內的所有任務地點,剛看到這個需求時有點懵逼,第一想到的就是要利用地球的半徑公式去計算距離,也就是把地球想成一個球體,去計算球上兩點之間的距離,可想而知,這樣的方法效率會比較低,每條資料都要來與本人的坐標做計算,太過繁瑣,經過大佬的指點,想到了用redis自帶的GEO來實作此功能,
實戰演習
以下是給大家準備的sql腳本
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for job_base_info
-- ----------------------------
DROP TABLE IF EXISTS `job_base_info`;
CREATE TABLE `job_base_info` (
`job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任務ID',
`job_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任務名稱',
`job_location` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '任務地點位置經緯度-逗號隔開',
`job_detail_address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任務詳細地點位置',
PRIMARY KEY (`job_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '作業任務詳情基礎資訊表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of job_base_info
-- ----------------------------
INSERT INTO `job_base_info` VALUES (1, '軟體開發', '120.433576,36.139697', '青島市嶗山區海爾路1號');
INSERT INTO `job_base_info` VALUES (2, '兒童攝影', '120.420297,36.156589', '山東省青島市李滄區書院路188號');
INSERT INTO `job_base_info` VALUES (3, '清潔家用電器', '120.025706,36.281478', '山東省青島市膠州市福州支路232號東60米');
INSERT INTO `job_base_info` VALUES (4, '辯論學習', '120.505042,36.171247', '松嶺路238號中國海洋大學內');
SET FOREIGN_KEY_CHECKS = 1;
廢話不多說,讓我們來看看具體的實作
(1)我們要在程式啟動時,將資料庫中的任務資料的坐標資訊初始化到redis中(此處暫且忽略任務的增刪改查對redis中資料的影響)
@PostConstruct
public void init(){
//首先要洗掉該key的所有值
redisTemplate.delete("company-task");
List<JobBaseInfo> jobBaseInfoList = jobBaseInfoMapper.selectList(Wrappers.emptyWrapper());
jobBaseInfoList.stream().forEach(item->{
String jobLocation = item.getJobLocation();
if(StrUtil.isNotEmpty(jobLocation)){
String[] split = jobLocation.split(",");comp
if(split.length==2){
//Point(經度, 緯度)
Point point = new Point(Double.parseDouble(split[0]),Double.parseDouble(split[1]));
//將經緯度資料及其id存到key為“company-task”中
redisTemplate.opsForGeo().add("company-task",point,item.getJobId());
}
}
});
}
(2)查詢當前坐標下3km范圍內的任務地點(外加根據任務名搜索的聯合查詢)
@Override
public List<JobBaseInfo> selectJobList(JobBaseInfoDTO jobBaseInfoDTO) {
String jobLocation = jobBaseInfoDTO.getJobLocation();
//距離
Double distance = jobBaseInfoDTO.getDistance();
List<Integer> idList = new ArrayList<>();
if(StringUtils.isNotNull(jobLocation) && StringUtils.isNotNull(distance)){
String[] split = jobLocation.split(",");
if(split.length==2){
//Point(經度, 緯度) Distance(距離量, 距離單位)
Circle circle = new Circle(new Point(Double.parseDouble(split[0]),Double.parseDouble(split[1])),
new Distance(distance, Metrics.KILOMETERS));
//params: key, Circle 獲取存盤到redis中的distance范圍內的所有任務地點資料
GeoResults radius = redisTemplate.opsForGeo().radius("company-task", circle);
List<GeoResult> contentList = radius.getContent();
if(contentList.size()>0){
contentList.stream().forEach(item->{
RedisGeoCommands.GeoLocation content = (RedisGeoCommands.GeoLocation) item.getContent();
idList.add((Integer) content.getName());
});
}
}
}
jobBaseInfoDTO.setIdList(idList);
return jobBaseInfoMapper.selectJobList(jobBaseInfoDTO);
}
selectJobList(jobBaseInfoDTO)方法的sql如下
<select id="selectJobList" resultType="com.itzyq.redislikes.model.entity.JobBaseInfo">
select
<include refid="Base_Column_List"></include>
from job_base_info
<where>
<if test="jobName!=null and jobName!=''">
and job_name like CONCAT("%",#{jobName},"%")
</if>
<if test="idList!=null and idList.size > 0 ">
and job_id in
<foreach collection="idList" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
到這兒我們就已經實作了“附近的人”的功能了,接下來就讓我們具體的了解一下Redis中的GEO都有哪些騷操作吧,gzh回復“pSearch”獲取原始碼呦!
GEO操作
Redis GEO 主要用于存盤地理位置資訊,并對存盤的資訊進行操作,該功能在 Redis 3.2 版本新增,GEO 是基于zset的一種擴展資料格式,Redis GEO 操作方法有:
- geoadd:添加地理位置的坐標,
- geopos:獲取地理位置的坐標,
- geodist:計算兩個位置之間的距離,
- georadius:根據用戶給定的經緯度坐標來獲取指定范圍內的地理位置集合,
- georadiusbymember:根據儲存在位置集合里面的某個地點獲取指定范圍內的地理位置集合,
- geohash:回傳一個或多個位置物件的 geohash 值,
1、geoadd
geoadd 用于存盤指定的地理空間位置,可以將一個或多個經度(longitude)、緯度(latitude)、位置名稱(member)添加到指定的 key 中,
geoadd 語法格式如下:
GEOADD key longitude latitude member [longitude latitude member ...]
以下實體中 key 為 Sicily,Palermo 和 Catania 為位置名稱 :
實體
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis>

2、geopos
geopos 用于從給定的 key 里回傳所有指定名稱(member)的位置(經度和緯度),不存在的回傳 nil,
geopos 語法格式如下:
GEOPOS key member [member ...]
實體
redis> GEOPOS Sicily Palermo Catania NonExisting
1) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
3) (nil)
redis>
注:也可以使用zrange回傳所有的位置元素而不帶經緯度資訊
redis> ZRANGE Sicily 0 -1
1) "Palermo"
2) "Catania"
redis>
3、geodist
geodist 用于回傳兩個給定位置之間的距離,
geodist 語法格式如下:
GEODIST key member1 member2 [m|km|ft|mi]
member1 member2 為兩個地理位置,
最后一個距離單位引數說明:
- m :米,默認單位,
- km :千米,
- mi :英里,
- ft :英尺,
實體: 計算 Palermo 與 Catania 之間的距離
redis> GEODIST Sicily Palermo Catania
"166274.1516"
redis> GEODIST Sicily Palermo Catania km
"166.2742"
redis> GEODIST Sicily Palermo Catania mi
"103.3182"
redis> GEODIST Sicily Foo Bar
(nil)
redis>
4、georadius、georadiusbymember
georadius 以給定的經緯度為中心, 回傳鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素,
georadiusbymember 和 GEORADIUS 命令一樣, 都可以找出位于指定范圍內的元素, 但是 georadiusbymember 的中心點是由給定的位置元素決定的, 而不是使用經度和緯度來決定中心點,
georadius 與 georadiusbymember 語法格式如下:
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
引數說明:
- m :米,默認單位,
- km :千米,
- mi :英里,
- ft :英尺,
- WITHDIST: 在回傳位置元素的同時, 將位置元素與中心之間的距離也一并回傳,
- WITHCOORD: 將位置元素的經度和維度也一并回傳,
- WITHHASH: 以 52 位有符號整數的形式, 回傳位置元素經過原始 geohash 編碼的有序集合分值, 這個選項主要用于底層應用或者除錯, 實際中的作用并不大,
- COUNT 限定回傳的記錄數,
- ASC: 查找結果根據距離從近到遠排序,
- DESC: 查找結果根據從遠到近排序,
georadius 實體
redis> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
2) "190.4424"
2) 1) "Catania"
2) "56.4413"
redis> GEORADIUS Sicily 15 37 200 km WITHCOORD
1) 1) "Palermo"
2) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis>
georadiusbymember 實體:
redis> GEOADD Sicily 13.583333 37.316667 "Agrigento"
(integer) 1
redis> GEORADIUSBYMEMBER Sicily Agrigento 100 km
1) "Agrigento"
2) "Palermo"
redis>
5、geohash
Redis GEO 使用 geohash 來保存地理位置的坐標,geohash 用于獲取一個或多個位置元素的 geohash 值,
geohash 語法格式如下:
GEOHASH key member [member ...]
實體:
redis> GEOHASH Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"
redis>
geo并沒有提供洗掉指令,但根據其底層是zset實作,我們可以使用zrem對資料進行洗掉
redis> ZREM Sicily Agrigento
"1"
redis>
Redis GEO JAVA API
有了以上GEO的操作,我們可以在java中找到對應的api
/**
* 將指定的地理空間位置(緯度、經度、名稱)添加到指定的key中,
*/
//params: key, Point(經度, 緯度), 地方名稱
Long addedNum = redisTemplate.opsForGeo().add("Sicily", new Point(13.361389,38.115556), "Palermo");
/**
* 從key里回傳所有給定位置元素的位置(經度和緯度),
*/
//params: key, 地方名稱...
List<Point> points = redisTemplate.opsForGeo().position("Sicily","Palermo","Catania");
/**
* 回傳兩個給定位置之間的距離,
*/
//params: key, 地方名稱1, 地方名稱2, 距離單位
Distance distance = redisTemplate.opsForGeo()
.distance("Sicily","Palermo","Catania", RedisGeoCommands.DistanceUnit.KILOMETERS);
/**
* 以給定的經緯度為中心, 回傳鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素,并給出所有位置元素與中心的平均距離,
*/
//Point(經度, 緯度) Distance(距離量, 距離單位)
Circle circle = new Circle(new Point(13.361389,38.115556), new Distance(200, Metrics.KILOMETERS));
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().
//包含距離,包含經緯度,升序前五個
includeDistance().includeCoordinates().sortAscending().limit(5);
//params: key, Circle, GeoRadiusCommandArgs
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius("Sicily",circle,args);
/**
* 以給定的城市為中心, 回傳鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素,并給出所有位置元素與中心的平均距離,
*/
//params: 距離量, 距離單位
Distance distance = new Distance(200,Metrics.KILOMETERS);
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance().includeCoordinates().sortAscending().limit(5);
//params: key, 地方名稱, Circle, GeoRadiusCommandArgs
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius("Sicily","Palermo",distance,args);
/**
* 回傳一個或多個位置元素的 Geohash 表示
*/
//params: key, 地方名稱...
List<String> results = redisTemplate.opsForGeo()
.hash("Sicily","Palermo","Catania");
看到這里,“附近的人”功能你一定掌握了吧!如果你覺得這篇文章對你有所幫助,請移步關注gzh“阿Q說代碼”或者直接聯系阿Q:qingqing-4132,阿Q期待你的到來!
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/286069.html
標籤:NoSQL
上一篇:Redis 的持久化
