Redis-GEO数据结构

GEO百科知识2个月前发布 GEO研究员
1,915 0

1.基本介绍

  • 描述:GEO就是Geolocation的简写形式,代表地理坐标。Redis在3.2版本中加入了对GEO的支持,允许存储地理坐标信息。
  • 功能:根据经纬度来检索数据

2.常见命令

GEOADD:添加地理空间信息

GEOADD key longitude latitude member [longitude latitude member …]

  • key:存储地理空间数据的键名。

  • longitude:经度(东经为正,西经为负)。

  • latitude:纬度(北纬为正,南纬为负)。

  • member:该地理位置的标识名。

实例:将北京、上海、深圳的经纬度加入到 locations 这个 key 下

GEOADD locations 116.397128 39.916527 "Beijing"

GEOADD locations 121.473701 31.230416 "Shanghai"

GEOSEARCH:在指定范围内搜索member,并按照与指定点之间的距离排序后返回

GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE

  • GEOSEARCH key #key存储地理空间数据的键名
  • BYLONLAT x y # 以经度 x、纬度 y 为中心
  • BYRADIUS 10 # 半径为 10(默认单位是米)
  • WITHDISTANCE # 返回每个结果到中心点的距离

实例:以北京市(经度 116.397128,纬度 39.916527)为中心,查找 locations 中 10 公里以内的地点,并返回这些地点距离中心点的实际距离。

GEOSEARCH locations BYLONLAT 116.397128 39.916527 BYRADIUS 10 km WITHDISTANCE

3.应用场景:附近商户(黑马点评)

B站链接:

https://www.bilibili.com/video/BV1cr4y1671t/?spm_id_from=333.337.search-card.all.click&vd_source=2f92ee0dce22dc523ae377fc5e033684GEO数据结构分析:

为避免存储过多的数据,增加redis存储开销,把商户按类型(typeId)进行分组,并且以该typeId作为key存入同一个GEO集合。其中,value可表示商户id,Score是根据每个memeber的经纬度计算出来的分数。

商户信息按typeId分组以GEO数据结构写入redis中

 void loadShopData() { // 1.查询店铺信息 mybatis-plus List<Shop> list = shopService.list(); // 2.把店铺分组,按照typeId分组,typeId一致的放到一个集合 Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId)); // 3.分批完成写入Redis 多个typeId 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 经度 纬度 member for (Shop shop : value) { // 3.3.1 先将数据加入location locations.add(new RedisGeoCommands.GeoLocation<>( shop.getId().toString(), new Point(shop.getX(), shop.getY()) )); } // stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(),shop.getY()), shop.getId().toString()); // 3.1.2 这里以location为接口批量传入 stringRedisTemplate.opsForGeo().add(key, locations); } }

附近商户(按商户类型typeId)查询

public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) { // 是否根据坐标查询 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()); } // 计算分页参数 int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE; int end = current * SystemConstants.DEFAULT_PAGE_SIZE; //查询redis GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE String key = SHOP_GEO_KEY + typeId; GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo() .search( key, GeoReference.fromCoordinate(x, y), new Distance(5000), // 创建一个新的 GeoSearchCommandArgs 实例,用于配置 GEO 查询参数 // 返回结果:shopId、distance 最多返回end条结果 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) { // 无下一页 返回 return Result.ok(Collections.emptyList()); } // 截取 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.根据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.返回shop return Result.ok(shops); }

© 版权声明

相关文章

暂无评论

none
暂无评论...