一、概述
Redis的Geo功能主要用于存儲(chǔ)地理位置信息,并對其進(jìn)行操作。該功能在Redis 3.2版本新增。Redis Geo操作方法包括:
- geoadd:添加地理位置的坐標(biāo);
- geopos:獲取地理位置的坐標(biāo);
- geodist:計(jì)算兩個(gè)位置之間的距離;
- georadius:根據(jù)用戶給定的經(jīng)緯度坐標(biāo)來獲取指定范圍內(nèi)的地理位置集合;
- georadiusbymember:根據(jù)儲(chǔ)存在位置集合里面的某個(gè)地點(diǎn)獲取指定范圍內(nèi)的地理位置集合;
- geohash:返回一個(gè)或多個(gè)位置對象的geohash值。
二、Redis Geo功能案例:
1、案例1
查找某個(gè)城市下的門店信息。
比如有的一家門店存儲(chǔ)的Redis結(jié)構(gòu)如下:
store:
- member: poscon
- member: shangxixxi
其中poscon表示該門店的坐標(biāo)信息,shangxixxi表示該門店的名稱。
現(xiàn)在需要查找某個(gè)城市下的門店信息,可以使用Redis的Geo功能實(shí)現(xiàn):
GEOADD store 116.406890 39.909195 "poscon" "shangxixxi"
GEOADD store 116.420853 39.892662 "poscon1" "shangxixxi1"
GEOADD store 116.401415 39.915726 "poscon2" "shangxixxi2"
GEOADD store 116.426988 39.919938 "poscon3" "shangxixxi3"
GEOADD store 116.410057 39.904425 "poscon4" "shangxixxi4"
GEORADIUS store 116.409729 39.908256 10000km WITHCOORD WITHDIST
以上代碼表示在(116.409729, 39.908256)半徑為10000km的圓內(nèi)查找門店信息,返回結(jié)果會(huì)包含每個(gè)門店的坐標(biāo)信息和距離??梢愿鶕?jù)返回結(jié)果篩選出自己需要的門店信息。
2、案例2
添加下面幾條數(shù)據(jù):
- 北京南站 ( 116.378248 39.865275
- 北京站 ( 116.42803 39.903738 )
- 北京西站(116.322287 39.893729 )
127.0.0.1:6379> GEOADD g1 116.378248 39.865275 bjn 116.42803 39.903738 bjz 116.322287 39.893729 bjx
(integer) 3
- 計(jì)算北京西站到北京站的距離
127.0.0.1:6379> GEODIST g1 bjz bjx km
"9.0916"
- 搜索天安門(116.397904 39.909005 )附近1km內(nèi)的所有火車站,并按照距離升序排序
127.0.0.1:6379> GEOSEARCH g1 FROMLONLAT 116.397904 39.909005 BYRADIUS 10 km WITHDIST
1) 1) "bjz"
2) "2.6361"
2) 1) "bjn"
2) "5.1452"
3) 1) "bjx"
2) "6.6723"
- 計(jì)算北京西的坐標(biāo)與hash值
127.0.0.1:6379> GEOPOS g1 bjz
1) 1) "116.42802804708480835"
2) "39.90373880538094653"
127.0.0.1:6379> GEOHASH g1 bjz
1) "wx4g12k21s0"
三、實(shí)戰(zhàn)搜索附近商鋪
1、接口名稱
2、商家列表展示距離
當(dāng)我們點(diǎn)擊美食之后,會(huì)出現(xiàn)一系列的商家,商家中可以按照多種排序方式,我們此時(shí)關(guān)注的是距離,這個(gè)地方就需要使用到我們的GEO,向后臺(tái)傳入當(dāng)前app收集的地址(我們此處是寫死的) ,以當(dāng)前坐標(biāo)作為圓心,同時(shí)綁定相同的店家類型type,以及分頁信息,把這幾個(gè)條件傳入后臺(tái),后臺(tái)查詢出對應(yīng)的數(shù)據(jù)再返回。
如圖:
3、約定數(shù)據(jù)存儲(chǔ)規(guī)則
我們要做的事情是:將數(shù)據(jù)庫表中的數(shù)據(jù)導(dǎo)入到redis中去,redis中的GEO,GEO在redis中就一個(gè)menber和一個(gè)經(jīng)緯度,我們把x和y軸傳入到redis做的經(jīng)緯度位置去,但我們不能把所有的數(shù)據(jù)都放入到menber中去,畢竟作為redis是一個(gè)內(nèi)存級數(shù)據(jù)庫,如果存海量數(shù)據(jù),redis還是力不從心,所以我們在這個(gè)地方存儲(chǔ)他的id即可。
但是這個(gè)時(shí)候還有一個(gè)問題,就是在redis中并沒有存儲(chǔ)type,所以我們無法根據(jù)type來對數(shù)據(jù)進(jìn)行篩選,所以我們可以按照商戶類型做分組,類型相同的商戶作為同一組,以typeId為key存入同一個(gè)GEO集合中即可
- 測試:
void loadShopData() {
// 1.查詢店鋪信息
List<Shop> list = shopService.list();
// 2.把店鋪分組,按照typeId分組,typeId一致的放到一個(gè)集合
Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
// 3.分批完成寫入Redis
for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
// 3.1.獲取類型id
Long typeId = entry.getKey();
String key = SHOP_GEO_KEY + typeId;
// 3.2.獲取同類型的店鋪的集合
List<Shop> value = entry.getValue();
List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(value.size());
// 3.3.寫入redis GEOADD key 經(jīng)度 緯度 member
for (Shop shop : value) {
// stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString());
locations.add(new RedisGeoCommands.GeoLocation<>(
shop.getId().toString(),
new Point(shop.getX(), shop.getY())
));
}
stringRedisTemplate.opsForGeo().add(key, locations);
}
}
- 結(jié)果:
4、實(shí)現(xiàn)附近商戶功能
4.1 第一步:導(dǎo)入pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-data-redis</artifactId>
<groupId>org.springframework.data</groupId>
</exclusion>
<exclusion>
<artifactId>lettuce-core</artifactId>
<groupId>io.lettuce</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.1.6.RELEASE</version>
</dependency>
4.2 第二步:寫接口ShopController
傳入?yún)?shù):文章來源:http://www.zghlxwxcb.cn/news/detail-470177.html
- typeId:商品類型id
- current: 當(dāng)前頁碼
- x : 經(jīng)度
- y : 緯度
@GetMapping("/of/type")
public Result queryShopByType(
@RequestParam("typeId") Integer typeId,
@RequestParam(value = "current", defaultValue = "1") Integer current,
@RequestParam(value = "x", required = false) Double x,
@RequestParam(value = "y", required = false) Double y
) {
return shopService.queryShopByType(typeId, current, x, y);
}
4.3 第三步:實(shí)現(xiàn)
- 判斷是否需要根據(jù)坐標(biāo)查詢
- 計(jì)算分頁參數(shù)
- 查詢r(jià)edis、按照距離排序、分頁。結(jié)果:shopId、distance
- 解析出id
- 根據(jù)id查詢Shop
- 返回店鋪數(shù)據(jù)
@Override
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
// 1.判斷是否需要根據(jù)坐標(biāo)查詢
if (x == null || y == null) {
// 不需要坐標(biāo)查詢,按數(shù)據(jù)庫查詢
Page<Shop> page = query()
.eq("type_id", typeId)
.page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
// 返回?cái)?shù)據(jù)
return Result.ok(page.getRecords());
}
// 2.計(jì)算分頁參數(shù)
int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
// 3.查詢r(jià)edis、按照距離排序、分頁。結(jié)果:shopId、distance
String key = SHOP_GEO_KEY + typeId;
GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo() // GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE
.search(
key,
GeoReference.fromCoordinate(x, y),
new Distance(5000),
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
);
// 4.解析出id
if (results == null) {
return Result.ok(Collections.emptyList());
}
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
if (list.size() <= from) {
// 沒有下一頁了,結(jié)束
return Result.ok(Collections.emptyList());
}
// 4.1.截取 from ~ end的部分
List<Long> ids = new ArrayList<>(list.size());
Map<String, Distance> distanceMap = new HashMap<>(list.size());
list.stream().skip(from).forEach(result -> {
// 4.2.獲取店鋪id
String shopIdStr = result.getContent().getName();
ids.add(Long.valueOf(shopIdStr));
// 4.3.獲取距離
Distance distance = result.getDistance();
distanceMap.put(shopIdStr, distance);
});
// 5.根據(jù)id查詢Shop
String idStr = StrUtil.join(",", ids);
List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
for (Shop shop : shops) {
shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}
// 6.返回
return Result.ok(shops);
}
四、源碼下載
https://gitee.com/charlinchenlin/koo-erp文章來源地址http://www.zghlxwxcb.cn/news/detail-470177.html
到了這里,關(guān)于Redis GEO功能詳細(xì)介紹與實(shí)戰(zhàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!