当前位置: 首页 > article >正文

GEO数据结构

目录

1. GEOADD

2. GEODIST

3. GEOHASH

3. GEOHASH

4. GEOPOS

6. GEOSEARCH

7. GEOSEARCHSTORE

应用场景

代码的逻辑分解:

比较难懂的部分:

Redis GEO 查询与分页

results 的结构:

分页处理与截取数据 

附加距离信息


1. GEOADD

功能:向指定的 key 中添加地理空间信息。

参数

  • 经度(longitude):地理位置的经度(范围:-180 到 180)。
  • 纬度(latitude):地理位置的纬度(范围:-85.05112878 到 85.05112878)。
  • 值(member):与此经纬度相关联的唯一标识符。

2. GEODIST

功能:计算两个位置之间的距离。

参数

  • 第一个 member。
  • 第二个 member。
  • 距离单位(可选):m(米)、km(千米)、mi(英里)、ft(英尺)。
  • 结果:返回两点之间的距离,单位为指定的单位。

3. GEOHASH

功能:返回指定成员的 GeoHash 值。

GeoHash 是一种将经纬度编码为字符串的算法,用于地理位置的高效存储和查询。

参数

  • 一个或多个 member。

结果:返回两点之间的距离,单位为指定的单位。

3. GEOHASH

功能:返回指定成员的 GeoHash 值。

GeoHash 是一种将经纬度编码为字符串的算法,用于地理位置的高效存储和查询。

参数

  • 一个或多个 member。

结果:返回一个字符串表示的 GeoHash 值。

4. GEOPOS

功能:返回指定成员的经纬度。

参数

  • 一个或多个 member。
结果:返回对应的经纬度数组,例如
[13.361389, 38.115556]

6. GEOSEARCH

功能:在指定的范围内搜索成员。

  • 参数
    • 中心点(可以是经纬度或某个 member)。
    • 范围(单位:mkm 等)。
    • 排序规则(ASCDESC)。
    • 可选参数:WITHDISTWITHCOORD

7. GEOSEARCHSTORE

功能:将 GEOSEARCH 的结果存储到新的 key 中。

  • 参数
    • 目标 key:存储结果的 key。
    • 源 key:原始数据的 key。
    • 查询条件:与 GEOSEARCH 相同。

应用场景

  1. 基于位置的服务(LBS):例如,寻找某地点附近的商店、餐馆或加油站。
  2. 物流管理:计算两个地址之间的距离。
  3. 社交应用:匹配同一城市或区域的用户。

注意事项

  • GEO 数据结构在存储时使用的是 Redis 的有序集合(Sorted Set),经纬度被编码为 52 位的 GeoHash,然后作为分值(score)存储。
  • 支持的查询范围有限,主要适用于地球范围内的点查询和距离计算。

代码的逻辑分解:

  1. 判断是否存在地理坐标 (xy):

    if(x == null && y == null){
        // 数据库分页查询
    }
    
  • 如果 xy 均为空,说明不需要按地理位置查询店铺,此时直接从数据库中按店铺类型(typeId)分页查询。
  • 分页参数:current 为当前页,分页大小为常量 SystemConstants.DEFAULT_PAGE_SIZE

 数据库分页查询逻辑:

Page<Shop> page = query()
    .eq("type_id", typeId)
    .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));

处理带有地理坐标的情况: 如果提供了地理坐标,则按以下步骤处理:

  • 计算分页范围:

    int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
    int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
    
  • 根据当前页计算数据截取的起始位置(from)和结束位置(end)。

  • 从 Redis 查询 GEO 数据:

    GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(
        key,
        GeoReference.fromCoordinate(x, y), // 圆心为地理坐标x, y
        new Distance(5000), // 搜索范围5km
        RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
    );
    
  • 通过 Redis 的 GEO 查询:

    • 按照距离排序。
    • 限制返回的最大结果数量为 end
    • 返回结果中包含店铺 ID 和距离。
  • 检查 Redis 查询结果是否为空:

    if(results == null) return Result.ok(Collections.emptyList());
    

    截取分页内容:

    List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = results.getContent();
    if(content.size() < from) return Result.ok(Collections.emptyList());
    

    检查总结果是否足够多以满足当前分页,如果不足则返回空列表。

提取店铺 ID 和距离:

content.stream().skip(from).forEach(result -> {
    String shopId = result.getContent().getName(); // 获取店铺ID
    Distance distance = result.getDistance(); // 获取距离
    ids.add(Long.valueOf(shopId));
    distanceMap.put(shopId, distance);
});
  • 使用 skip(from) 跳过 from 之前的结果。
  • 将分页内的店铺 ID 和距离分别存入 idsdistanceMap

附加距离信息:

for (Shop shop : shops) {
    shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}

返回查询结果:

return Result.ok(shops);

比较难懂的部分:

Redis GEO 查询与分页

GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(
    key,                                       // Redis 中存储地理位置数据的键
    GeoReference.fromCoordinate(x, y),        // 查询的圆心坐标 (经度, 纬度)
    new Distance(5000),                       // 查询的半径范围为 5000 米(5 公里)
    RedisGeoCommands.GeoSearchCommandArgs     // 额外的查询参数
        .newGeoSearchArgs()
        .includeDistance()                    // 返回结果中包含距离
        .limit(end)                           // 限制返回的最多结果数为 end
);
  • 作用: 使用 Redis 的 GEO 数据结构查询指定范围内的地理位置,并按距离排序。limit(end) 表示查询的结果最多为 end 条。
  • 这段代码的核心作用是基于地理位置的店铺查询:
    • 找到以 (x, y) 为中心,半径 5 公里的店铺。
    • 按照距离排序,最多返回 end 条记录。
    • 每条记录包含店铺 ID 和距离信息。

resultsGeoResults<RedisGeoCommands.GeoLocation<String>> 类型,表示 Redis GEO 查询的结果集。它包含了多个 GeoResult 对象,每个 GeoResult 对应一个位置的详细信息。

我们来具体说明 results 中的内容及其结构。

results 的结构:

results 的类型是 GeoResults<RedisGeoCommands.GeoLocation<String>>,它包含:

  • content:查询结果的列表,是一个 List<GeoResult<RedisGeoCommands.GeoLocation<String>>>

每个 GeoResult 包括:

  • content:具体的位置信息,类型是 RedisGeoCommands.GeoLocation<String>
    • 包含位置的唯一标识(如店铺 ID)。
  • distance:从查询圆心到该位置的距离,类型是 Distance
results
├── content: List<GeoResult<RedisGeoCommands.GeoLocation<String>>>
    ├── GeoResult 1
    │   ├── content: RedisGeoCommands.GeoLocation<String>
    │   │   ├── name: "101"   // 店铺 ID
    │   │   ├── point: Point(x=120.5, y=30.0) // 经纬度坐标
    │   ├── distance: Distance(value=1200.0, unit=METERS) // 距离
    ├── GeoResult 2
    │   ├── content: RedisGeoCommands.GeoLocation<String>
    │   │   ├── name: "102"
    │   │   ├── point: Point(x=120.6, y=30.1)
    │   ├── distance: Distance(value=1500.0, unit=METERS)
    └── ...
  • 难点:
    • Redis GEO 查询结果不直接支持分页,因此需要手动跳过一部分数据(skip(from))以实现分页效果。
分页处理与截取数据 

以下代码的主要作用是从 content 中提取分页后的店铺信息,并将店铺的 ID距离 分别存储到两个集合中,供后续使用。distanceMap 是一个 Map<String, Distance>,用于记录每个店铺的 ID 和距离。ids 是一个 ArrayList<Long>,存储所有分页后的店铺 ID。

content.stream().skip(from).forEach(result -> {
    String shopId = result.getContent().getName();
    Distance distance = result.getDistance();
    ids.add(Long.valueOf(shopId));
    distanceMap.put(shopId, distance);
});
  • 作用: 手动处理分页逻辑,跳过 from 条数据,提取目标页的数据。
  • 难点: 理解为什么需要 skip(from),因为 Redis 查询结果已经包含了 end 条数据,但需要从 from 开始取当前页。
附加距离信息
for (Shop shop : shops) {
    shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}
  • 遍历从数据库查询出来的每个店铺。
  • 根据店铺的 ID,从 distanceMap 中找到对应的距离值。
  • 将距离值设置到当前店铺对象的 distance 属性中。

http://www.kler.cn/a/565032.html

相关文章:

  • DeepSeek 开源狂欢周(一)FlashMLA:高效推理加速新时代
  • vue从入门到精通(十六):自定义指令
  • 神经网络中感受野的概念和作用
  • 浅谈C++/C命名冲突
  • 跟着AI学vue第十一章
  • 面试JAVA集合常用方法总结
  • 微芯-AVR内核单片机
  • android 新增native binder service 方式(一)
  • PHP如何与HTML结合使用?
  • 在 JMeter 中使用 Python 脚本
  • Qt常用控件之下拉框QComboBox
  • 【每日前端面试-02】
  • Unity学习笔记之——ugui的性能优化
  • 一键导出数据库表到Excel
  • GDidees CMS v3.9.1本地文件泄露漏洞(CVE-2023-27179)
  • [原创]openwebui解决searxng通过接口请求不成功问题
  • C++游戏开发系列教程之第二篇:面向对象编程与游戏架构设计
  • 27.贪心算法5
  • uni小程序wx.switchTab有时候跳转错误tab问题,解决办法
  • BladeX框架接口请求跨域