GEO数据结构

news/2025/2/25 18:50:48

目录

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.niftyadmin.cn/n/5865850.html

相关文章

基于 IMX6ULL 的环境监测自主调控系统

文章目录 前言一、项目介绍二、前台QT界面1. 界面设计2. 代码示例 三、后台硬件驱动四、JsonRPC 实现前后台分离1. 为什么要拆分&#xff1f;2. 如何拆分&#xff1f; 五、总结 前言 项目完整代码&#xff1a;基于 IMX6ULL 的环境监测自主调控系统完整代码 该项目的源代码适用…

力扣leetcode 21. 合并两个有序链表 递归 C语言解法

题目&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输…

使用Python爬虫获取淘宝商品详情:API返回值说明与案例指南

在电商数据分析和运营中&#xff0c;获取淘宝商品详情是常见的需求。淘宝开放平台提供了丰富的API接口&#xff0c;允许开发者通过合法的方式获取商品信息。本文将详细介绍如何使用Python编写爬虫&#xff0c;通过淘宝API获取商品详情&#xff0c;并解析API返回值的含义和结构。…

第二章:辅助功能

目录 第一节&#xff1a;工作目录创建 第二节&#xff1a;属性定义 第三节&#xff1a;日志宏 第四节&#xff1a; SqliteHelper 第五节&#xff1a; FileHelper 下期预告&#xff1a; 第一节&#xff1a;工作目录创建 在家目录创建一个名为mq的目录&#xff0c;mq里又创…

WSL2使用Kind创建K8S集群时出现IPV6网络创建失败

机器信息&#xff1a; ubuntu 22.04 kind 版本 0.17.0 通过命令创建k8s集群 kind create cluster --image kindest/node:v1.25.3 --name aio -v 5 出现如下报错&#xff1a; ERROR: failed to create cluster: failed to ensure docker network: command "docker ne…

Threejs教程一【三要素】

场景 场景是一个容器&#xff0c;用于容纳所有的物体、光源、相机等元素。 // 创建场景 const scene new THREE.Scene(); //修改背景颜色&#xff0c;颜色支持十六进制、rgb、hsl、贴图等 scene.background new THREE.Color(0x000000);相机 相机决定了渲染的结果&#xff…

DeepSeek开源FlashMLA:颠覆大模型训练效率的新一代技术解析

在AI领域&#xff0c;大模型的训练成本与效率始终是开发者面临的“阿喀琉斯之踵”。动辄千亿参数的模型需要消耗数月时间与数百万美元的计算资源&#xff0c;严重制约了技术创新。2023年&#xff0c;中国AI公司深度求索&#xff08;DeepSeek&#xff09;开源的FlashMLA框架&…

在Ubuntu下通过Docker部署PSQL服务器

嘿&#xff0c;朋友们&#xff0c;今天我们来聊聊如何在Ubuntu上通过Docker部署PostgreSQL&#xff08;PSQL&#xff09;服务器。Docker让我们可以轻松管理应用程序的环境&#xff0c;而PostgreSQL是个强大的开源关系数据库。它以其稳定性、扩展性和丰富的功能而著称&#xff0…