GEOADD:添加一个地理空间信息,包含:经度(longitude)、纬度(latitude)、值(member)
GEOADD key longitude latitude member [longitude latitude member …]
GEOADD china 13.361389 38.115556 "shanghai" 15.087269 37.502669 "beijing"
GEODIST:计算指定的两个点之间的距离并返回
GEODIST key member1 member2 [m|km|ft|mi]
GEODIST china beijing shanghai km
GEOHASH:将指定member的坐标转化为hash字符串形式并返回
GEOHASH key member [member …]
云服务器:0>GEOHASH china beijing shanghai1) "sqdtr74hyu0"2) "sqc8b49rny0"
GEOPOS:返回指定member的坐标
云服务器:0>geopos china beijing shanghai
1) 1) "15.08726745843887329"
2) "37.50266842333162032"
2) 1) "13.36138933897018433"
2) "38.11555639549629859"
GEOGADIUS:指定圆心、半径,找到该园内包含的所有member,并按照与圆心之间的距离排序后返回,6.2之后已废弃
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
云服务器:0>GEORADIUS china 15 37 200 km WITHDIST WITHCOORD
1) 1) "shanghai"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "beijing"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
云服务器:0>GEORADIUS china 15 37 200 km WITHDIST
1) 1) "shanghai"
2) "190.4424"
2) 1) "beijing"
2) "56.4413"
GEOSEARCH:在指定范围内搜索member,并按照与制定点之间的距离排序后返回,范围可以使圆形或矩形,6.2的新功能
命令格式 GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius m|km|ft|mi] [BYBOX width height m|km|ft|mi] [ASC|DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]
云服务器:0>geosearch china FROMLONLAT 15 37 BYRADIUS 200 km ASC WITHCOORD WITHDIST
1) 1) "beijing"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
2) 1) "shanghai"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
云服务器:0>geosearch china FROMLONLAT 15 37 BYBOX 400 400 km DESC WITHCOORD WITHDIST
1) 1) "shanghai"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "beijing"
2) "56.4413"
3) S1) "15.08726745843887329"
2) "37.50266842333162032"
GEOSEARCHSTORE:与GEOSEARCH功能一致,不过可以把结果存储到一个指定的key,也是6.2的新功能
命令格式 GEOSEARCHSTORE destination source [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius m|km|ft|mi] [BYBOX width height m|km|ft|mi] [ASC|DESC] [COUNT count [ANY]] [STOREDIST]
这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点
指定成员的位置被用作查询的中心。
关于 GEORADIUSBYMEMBER 命令的更多信息, 请参考 GEORADIUS 命令的文档
复杂度:O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index
****云服务器:0>GEORADIUSBYMEMBER china beijing 200 km1) "shanghai"2) "beijing"
Key | Value | Score |
---|---|---|
shop:geo:美食 | 海底捞 | 40691512240174598 |
吉野家 | 40691519846517915 | |
shop:geo:KTV | KTV 01 | 40691165486458787 |
KTV 02 | 40691514154651657 |
@Test
public void loadShopData(){
//1. 查询所有店铺信息
List<Shop> shopList = shopService.list();
//2. 按照typeId,将店铺进行分组
Map<Long, List<Shop>> map = shopList.stream().collect(Collectors.groupingBy(Shop::getTypeId));
//3. 逐个写入Redis
for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
//3.1 获取类型id
Long typeId = entry.getKey();
//3.2 获取同类型店铺的集合
List<Shop> shops = entry.getValue();
String key = SHOP_GEO_KEY + typeId;
for (Shop shop : shops) {
//3.3 写入redis GEOADD key 经度 纬度 member
stringRedisTemplate.opsForGeo().add(key,new Point(shop.getX(),shop.getY()),shop.getId().toString());
}
}
}
是上面的代码不够优雅,是一条一条写入的,效率较低,那我们现在来改进一下,这样只需要写入等同于type_id数量的次数
@Test
public void loadShopData() {
List<Shop> shopList = shopService.list();
Map<Long, List<Shop>> map = shopList.stream().collect(Collectors.groupingBy(Shop::getTypeId));
for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
Long typeId = entry.getKey();
List<Shop> shops = entry.getValue();
String key = SHOP_GEO_KEY + typeId;
List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(shops.size());
for (Shop shop : shops) {
//将当前type的商铺都添加到locations集合中
locations.add(new RedisGeoCommands.GeoLocation<>(shop.getId().toString(), new Point(shop.getX(), shop.getY())));
}
//批量写入
stringRedisTemplate.opsForGeo().add(key, locations);
}
}
• 代码编写完毕,我们启动测试方法,然后去Redis图形化界面中查看是否有对应的数据
<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>
看样子是ShopController中的方法,那我们现在来修改其代码,除了typeId,分页码,我们还需要其坐标
@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);
}
具体业务逻辑依旧是写在ShopServiceImpl中
@Override
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
//1. 判断是否需要根据距离查询
if (x == null || y == null) {
// 根据类型分页查询
Page<Shop> page = query()
.eq("type_id", typeId)
.page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
// 返回数据
return Result.ok(page.getRecords());
}
//2. 计算分页查询参数
int from = (current - 1) * SystemConstants.MAX_PAGE_SIZE;
int end = current * SystemConstants.MAX_PAGE_SIZE;
String key = SHOP_GEO_KEY + typeId;
//3. 查询redis、按照距离排序、分页; 结果:shopId、distance
//GEOSEARCH key FROMLONLAT x y BYRADIUS 5000 m WITHDIST
GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(key,
GeoReference.fromCoordinate(x, y),
new Distance(5000),
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end));
if (results == null) {
return Result.ok(Collections.emptyList());
}
//4. 解析出id
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
if (list.size() < from) {
//起始查询位置大于数据总量,则说明没数据了,返回空集合
return Result.ok(Collections.emptyList());
}
ArrayList<Long> ids = new ArrayList<>(list.size());
HashMap<String, Distance> distanceMap = new HashMap<>(list.size());
list.stream().skip(from).forEach(result -> {
String shopIdStr = result.getContent().getName();
ids.add(Long.valueOf(shopIdStr));
Distance distance = result.getDistance();
distanceMap.put(shopIdStr, distance);
});
//5. 根据id查询shop
String idsStr = StrUtil.join(",", ids);
List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD( id," + idsStr + ")").list();
for (Shop shop : shops) {
//设置shop的举例属性,从distanceMap中根据shopId查询
shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}
//6. 返回
return Result.ok(shops);
}