User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   SQL   ›   NOSQL   ›   Redis   ›   正文
Redis

Redis—Geo功能实现查找附近的位置

2020-10-23 12:24:30
1021  0 1
参考目录 隐藏
1) 1、前言
2) 2、Redis 中的 GEO
3) 3、 常用操作
4) 3.1、GEOADD,添加成员的经纬度信息
5) 3.2、GEODIST,计算成员间距离
6) 3.3、GEORADIUS 基于经纬度坐标的范围查询
7) 3.4、GEORADIUSBYMEMBER 基于成员位置范围查询
8) 3.5、GEOPOS,获取成员经纬度
9) 3.6、GEOHASH 计算经纬度Hash
10) 4、基于 Redis GEO 实战
11) 4.1 开发环境
12) 4.2 批量添加位置信息
13) 4.3 查询附近的特定位置
14) 4.4 删除元素
15) 5、总结

阅读完需:约 9 分钟

1、前言

我们平时骑共享单车或者是找酒店,餐馆的时候总是会用到查找附近的功能。

地图后台如何根据自己所在位置查询来查询附近的呢? 显然通过经纬度坐标来查询。

其次为什么不用MySQL呢?MySQL是我们首先能够想到的,毕竟大部分数据要持久化到MySQL。但是使用MySQL需要自行计算Geohash。需要使用大量数学几何计算,并且需要学习地理相关知识,门槛较高,短时间内不可能完成需求,而且长期来看这也不是MySQL擅长的领域,所以没有考虑它。 (PG数据库的GIS也可以实现经纬度的操作)

Geohash 参考 https://www.cnblogs.com/LBSer/p/3310455.html

2、Redis 中的 GEO

Redis是我们最为熟悉的K-V数据库,它常被拿来作为高性能的缓存数据库来使用,大部分项目都会用到它。从3.2版本开始它开始提供了GEO能力,用来实现诸如附近位置、计算距离等这类依赖于地理位置信息的功能。GEO相关的命令如下:

Redis 命令 描述
GEOHASH 返回一个或多个位置元素的 Geohash 表示
GEOPOS 从 key 里返回所有给定位置元素的位置(经度和纬度)
GEODIST 返回两个给定位置之间的距离
GEORADIUS 以给定的经纬度为中心, 找出某一半径内的元素
GEOADD 将指定的地理空间位置(纬度、经度、名称)添加到指定的 key 中
GEORADIUSBYMEMBER 找出位于指定范围内的元素,中心点是由给定的位置元素决定

Redis 会假设地球为完美的球形, 所以可能有一些位置计算偏差,据说<=0.5%,对于有严格地理位置要求的需求来说要经过一些场景测试来检验是否能够满足需求。

GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

3、 常用操作

3.1、GEOADD,添加成员的经纬度信息

>  geoadd citys 125.19 43.54 changchun
>  geoadd citys 122.50 45.38 baicheng
>  geoadd citys 126.26 41.56 baishan
>  geoadd citys 124.18 45.30 daan
>  geoadd citys 125.42 44.32 dehui 
>  geoadd citys 128.13 43.22 dunhua
>  geoadd citys 124.49 43.31 gongzhuling 
>  geoadd citys 129.00 42.32 helong
>  geoadd citys 126.44 42.58 huadian
>  geoadd citys 130.22 42.52 hunchun
>  geoadd citys 126.11 41.08 jian
>  geoadd citys 127.21 43.42 jiaohe 
>  geoadd citys 126.33 43.52 jilin
>  geoadd citys 125.51 44.09 jiutai
>  geoadd citys 125.09 42.54 liaoyuan
>  geoadd citys 126.53 41.49 linjiang
>  geoadd citys 129.26 42.46 longjing
>  geoadd citys 125.40 42.32 meihekou
>  geoadd citys 126.57 44.24 shulan
>  geoadd citys 124.22 43.10 siping
>  geoadd citys 124.49 45.11 songyuan
>  geoadd citys 122.47 45.20 taoyan
>  geoadd citys 125.56 41.43 tonghua
>  geoadd citys 129.51 42.57 tumen
>  geoadd citys 129.30 42.54 yanjin
>  geoadd citys 117.12 39.08 tianjin 114.29 38.02  shijiazhuang

意思是将经度为117.12纬度为39.08的地点tianjin和经度为114.29纬度为38.02的地点shijiazhuang加入key为citys的 sorted set集合中。可以添加一到多个位置。然后我们就可以借助于其他命令来进行地理位置的计算了。

有效的经度从-180 度到 180 度。有效的纬度从-85.05112878 度到 85.05112878 度。当坐标位置超出上述指定范围时,该命令将会返回一个错误。

3.2、GEODIST,计算成员间距离

语法:

GEODIST key member1 member2 [unit]

unit 为结果单位,可选,支持:m,km,mi,ft,分别表示米(默认),千米,英里,英尺。

计算演示,计算长春到敦化的距离:

>  GEODIST citys changchun dunhua
"240309.2820"

>  GEODIST citys changchun dunhua km
"240.3093"

3.3、GEORADIUS 基于经纬度坐标的范围查询

语法:

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

检索以某个经纬度为圆心,在特定半径的圆形范围内的成员。

查询演示,以经纬度125,42为圆心,100公里范围内的成员:

>  GEORADIUS citys 125 42 100 km
1) "tonghua"
2) "meihekou"
3) "liaoyuan"

这个命令比GEOADD要复杂一些:

  • radius 半径长度,必选项。后面的m、km、ft、mi、是长度单位选项,四选一。
  • WITHCOORD 将位置元素的经度和维度也一并返回,非必选。
  • WITHDIST 在返回位置元素的同时, 将位置元素与中心点的距离也一并返回。距离的单位和查询单位一致,非必选。
  • WITHHASH 返回位置的 52 位精度的Geohash值,非必选。这个我反正很少用,可能其它一些偏向底层的LBS应用服务需要这个。
  • COUNT 返回符合条件的位置元素的数量,非必选。比如返回前 10 个,以避免出现符合的结果太多而出现性能问题。
  • ASC|DESC 排序方式,非必选。默认情况下返回未排序,但是大多数我们需要进行排序。参照中心位置,从近到远使用ASC ,从远到近使用DESC。

例如,我们在 cities:locs 中查找以(115.03,38.44)为中心,方圆200km的城市,结果包含城市名称、对应的坐标和距离中心点的距离(km),并按照从近到远排列。命令如下:

redis> georadius cities:locs 115.03 38.44 200 km WITHCOORD WITHDIST ASC
1) 1) "shijiazhuang"
   2) "79.7653"
   3) 1) "114.29000169038772583"
      2) "38.01999994251037407"
2) 1) "tianjin"
   2) "186.6937"
   3) 1) "117.02000230550765991"
      2) "39.0800000535766543"

3.4、GEORADIUSBYMEMBER 基于成员位置范围查询

语法:

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST ke##y]

检索以某个成员为圆心,在特定半径的圆形范围内的成员。功能与 GEORADIUS 类似,只不过圆心为某个成员位置。

查询演示,以经纬度 changchun 为圆心,100公里范围内的成员:

> GEORADIUSBYMEMBER citys changchun 100 km
1) "siping"
2) "gongzhuling"
3) "changchun"
4) "jilin"
5) "jiutai"
6) "dehui"

3.5、GEOPOS,获取成员经纬度

语法:

GEOPOS key member [member …]

获取某个成员经纬度:

> GEOPOS citys changchun
1) "125.19000023603439"
2) "43.539999086145414"

3.6、GEOHASH 计算经纬度Hash

语法:

GEOHASH key member [member …]

获取将经纬度坐标生成的HASH字符串。

> GEOHASH citys changchun
1) "wz9p8y0wfk0"

GEOHASH,是表示坐标的一种方法,便于检索,存储。

4、基于 Redis GEO 实战

大致的原理思路说完了,接下来就是实操了。结合Spring Boot应用我们应该如何做?

4.1 开发环境

需要具有GEO特性的Redis版本,这里我使用的是Redis 4 。另外我们客户端使用 spring-boot-starter-data-redis 。这里我们会使用到 RedisTemplate对象。

4.2 批量添加位置信息

第一步,我们需要将位置数据初始化到Redis中。在Spring Data Redis中一个位置坐标(lng,lat) 可以封装到org.springframework.data.geo.Point对象中。然后指定一个名称,就组成了一个位置Geo信息。RedisTemplate提供了批量添加位置信息的方法。我们可以将添加命令转换为下面的代码:

  Map<String, Point> points = new HashMap<>();
   points.put("tianjin", new Point(117.12, 39.08));
   points.put("shijiazhuang", new Point(114.29, 38.02));
   // RedisTemplate 批量添加 Geo
   redisTemplate.boundGeoOps("cities:locs").add(points);

可以结合Spring Boot 提供的 ApplicationRunner 接口来实现初始化。

@Bean
public ApplicationRunner cacheActiveAppRunner(RedisTemplate<String, String> redisTemplate) {

    return args -> {
        final String GEO_KEY = "cities:locs";

        // 清理缓存
        redisTemplate.delete(GEO_KEY);

        Map<String, Point> points = new HashMap<>();
        points.put("tianjin", new Point(117.12, 39.08));
        points.put("shijiazhuang", new Point(114.29, 38.02));
        // RedisTemplate 批量添加 GeoLocation
        BoundGeoOperations<String, String> geoOps = redisTemplate.boundGeoOps(GEO_KEY);
        geoOps.add(points);
    };
}

4.3 查询附近的特定位置

RedisTemplate 针对GEORADIUS命令也有封装:

GeoResults<GeoLocation<M>> radius(K key, Circle within, GeoRadiusCommandArgs args)

Circle对象是封装覆盖的面积(图 1),需要的要素为中心点坐标Point对象、半径(radius)、计量单位(metric), 例如:

Point point = new Point(115.03, 38.44);

Metric metric = RedisGeoCommands.DistanceUnit.KILOMETERS;
Distance distance = new Distance(200, metric);

Circle circle = new Circle(point, distance);

GeoRadiusCommandArgs用来封装GEORADIUS的一些可选命令参数,(详情见上面)例如我们需要在返回结果中包含坐标、中心距离、由近到远排序的前 5 条数据:

RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands
        .GeoRadiusCommandArgs
        .newGeoRadiusArgs()
        .includeDistance()
        .includeCoordinates()
        .sortAscending()
        .limit(limit);

然后执行 radius方法就会拿到GeoResults<RedisGeoCommands.GeoLocation<String>>封装的结果,我们对这个可迭代对象进行解析就可以拿到我们想要的数据:

GeoResults<RedisGeoCommands.GeoLocation<String>> radius = redisTemplate.opsForGeo()
        .radius(GEO_STAGE, circle, args);

if (radius != null) {
    List<StageDTO> stageDTOS = new ArrayList<>();
    radius.forEach(geoLocationGeoResult -> {
        RedisGeoCommands.GeoLocation<String> content = geoLocationGeoResult.getContent();
        //member 名称  如  tianjin
        String name = content.getName();
        // 对应的经纬度坐标
        Point pos = content.getPoint();
        // 距离中心点的距离
        Distance dis = geoLocationGeoResult.getDistance();
    });
}

4.4 删除元素

有时候我们可能需要删除某个位置元素,但是Redis的Geo并没有删除成员的命令。不过由于它的底层是zset,我们可以借助zrem命令进行删除,对应的Java代码为:

redisTemplate.boundZSetOps(GEO_STAGE).remove("tianjin");

5、总结

我们使用Redis的Geo特性实现了常见的附近的地理信息查询需求,简单易上手。其实使用另一个Nosql数据库MongoDB也可以实现。在数据量比较小的情况下Redis已经能很好的满足需要。如果数据量大可使用MongoDB来实现。

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 594147浏览

随机文章
一致性协议
1年前
SpringBoot—操作 Redis
5年前
SpringBoot—单元测试
5年前
SpringMVC—Web九大组件之HandlerExceptionResolver异常处理器
3年前
MyBatis笔记9—parameterType(输入的参数类型)
5年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 594147 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付