Redis使用Geo存储经纬度

本文详细介绍了 Redis 的 Geo 功能,包括 GeoHash 算法原理及其应用,并通过 Spring Boot 项目的实例演示了如何实现地理位置数据的添加、查询、距离计算等功能。



Geo原理简单介绍

GeoHash 算法将 二维的经纬度 数据映射到 一维 的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。当我们想要计算 「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。

它的核心思想就是把整个地球看成是一个 二维的平面,然后把这个平面不断地等分成一个一个小的方格,每一个 坐标元素都位于其中的 唯一一个方格 中,等分之后的 方格越小,那么坐标也就 越精确

RedisGeo 数据结构,它们将 全部放在一个 zset 集合中。在 Redis 的集群环境中,集合可能会从一个节点迁移到另一个节点,如果单个 key 的数据过大,会对集群的迁移工作造成较大的影响,在集群环境中单个 key 对应的数据量不宜超过 1M,否则会导致集群迁移出现卡顿现象,影响线上服务的正常运行。

所以,这里建议 Geo 的数据使用 单独的 Redis 实例部署,不使用集群环境,如果数据量过亿甚至更大,就需要对 Geo 数据进行拆分,按国家拆分、按省拆分,按市拆分。

Geo常用方法讲解:

这里使用的是spring boot项目。了解如何在spring boot中使用Redis.

add方法:添加地理位置坐标

Point point = new Point(112.24292267773436, 30.337854070185536);//使用point定位经纬度
GeoLocation<String> geo = new GeoLocation<String>("荆州", point);
opsForGeo.add(key, geo);

point = new Point(111.28711213085936, 30.688053698037617);
opsForGeo.add(key, point, "宜昌");

Map<String,Point> memberCoordinateMap = new HashMap<String, Point>();
memberCoordinateMap.put("武汉", new Point(114.29736603710936, 30.59352961254808));
memberCoordinateMap.put("襄阳", new Point(112.12207306835936, 32.00160906017225));
memberCoordinateMap.put("荆门", new Point(112.216009,31.027841));
opsForGeo.add(key, memberCoordinateMap);

pos方法:给定的 key 里返回所有指定名称(member)的位置(经度和纬度)

List<Point> position = opsForGeo.position(key, "荆州","宜昌");
position.forEach(System.out::println);

//dist方法:计算两个位置之间的距离。
Distance distance = opsForGeo.distance(key, "荆州", "宜昌",RedisGeoCommands.DistanceUnit.KILOMETERS);
System.out.println("荆州与宜昌的距离:"+distance.getValue()+"KM");


radius方法:根据给定的位置名称来获取指定范围内的地理位置集合。

GeoRadiusCommandArgs geoRadiusCommandArgs = GeoRadiusCommandArgs.newGeoRadiusArgs()
        .sortAscending()// 排序
        .limit(3)// 输出元素的个数
        .includeCoordinates()// 输出经纬度
        .includeDistance();// 距离
// 封装距离参数
distance = new Distance(100d, RedisGeoCommands.DistanceUnit.KILOMETERS);
GeoResults<GeoLocation<String>> radius = opsForGeo.radius(key, "荆州", distance, geoRadiusCommandArgs);
radius.getContent().forEach(s -> System.out.println("名称:" + s.getContent().getName() + "  经纬度:" + s.getContent().getPoint() + "  距离:" + s.getDistance()));

radius方法:根据给定的经纬度坐标来获取指定范围内的地理位置集合。

point = new Point(111.28711213085936, 30.688053698037617);
Circle circle = new Circle(new Point(112.24292267773436, 30.337854070185536), distance);
GeoResults<GeoLocation<String>> radius2 = opsForGeo.radius(key, circle,geoRadiusCommandArgs);
radius2.getContent().forEach(s -> System.out.println("名称:" + s.getContent().getName() + "  经纬度:" + s.getContent().getPoint() + "  距离:" + s.getDistance()));

hash方法:获取位置的hash值

List<String> hash = opsForGeo.hash(key, "荆州","宜昌");
hash.forEach(System.out::println);

完整的demo

/**
	 * GeoHash 算法将 二维的经纬度 数据映射到 一维 的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。
	 * 当我们想要计算 「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。
	 * 参考:https://www.it610.com/article/1288713480500748288.htm
	 * @描述:
	 * @作者:严磊
	 * @时间:2020年11月25日 下午11:11:15
	 */
	public void GeoHash(){
		String key = "湖北";
		GeoOperations<String, String> opsForGeo = redisTemplate.opsForGeo();
		
		//add方法:添加地理位置坐标
		Point point = new Point(112.24292267773436, 30.337854070185536);//使用point定位经纬度
		GeoLocation<String> geo = new GeoLocation<String>("荆州", point);
		opsForGeo.add(key, geo);
		
		point = new Point(111.28711213085936, 30.688053698037617);
		opsForGeo.add(key, point, "宜昌");
		
		Map<String,Point> memberCoordinateMap = new HashMap<String, Point>();
		memberCoordinateMap.put("武汉", new Point(114.29736603710936, 30.59352961254808));
		memberCoordinateMap.put("襄阳", new Point(112.12207306835936, 32.00160906017225));
		memberCoordinateMap.put("荆门", new Point(112.216009,31.027841));
		opsForGeo.add(key, memberCoordinateMap);
		
		//pos方法:给定的 key 里返回所有指定名称(member)的位置(经度和纬度)
		List<Point> position = opsForGeo.position(key, "荆州","宜昌");
		position.forEach(System.out::println);
		
		//dist方法:计算两个位置之间的距离。
		Distance distance = opsForGeo.distance(key, "荆州", "宜昌",RedisGeoCommands.DistanceUnit.KILOMETERS);
		System.out.println("荆州与宜昌的距离:"+distance.getValue()+"KM");
		
		//radius方法:根据给定的位置名称来获取指定范围内的地理位置集合。
	    GeoRadiusCommandArgs geoRadiusCommandArgs = GeoRadiusCommandArgs.newGeoRadiusArgs()
	            .sortAscending()// 排序
	            .limit(3)// 输出元素的个数
	            .includeCoordinates()// 输出经纬度
	            .includeDistance();// 距离
	    // 封装距离参数
	    distance = new Distance(100d, RedisGeoCommands.DistanceUnit.KILOMETERS);
	    GeoResults<GeoLocation<String>> radius = opsForGeo.radius(key, "荆州", distance, geoRadiusCommandArgs);
	    radius.getContent().forEach(s -> System.out.println("名称:" + s.getContent().getName() + "  经纬度:" + s.getContent().getPoint() + "  距离:" + s.getDistance()));
	    
	    //radius方法:根据给定的经纬度坐标来获取指定范围内的地理位置集合。
	    point = new Point(111.28711213085936, 30.688053698037617);
	    Circle circle = new Circle(new Point(112.24292267773436, 30.337854070185536), distance);
	    GeoResults<GeoLocation<String>> radius2 = opsForGeo.radius(key, circle,geoRadiusCommandArgs);
	    radius2.getContent().forEach(s -> System.out.println("名称:" + s.getContent().getName() + "  经纬度:" + s.getContent().getPoint() + "  距离:" + s.getDistance()));
	    
	    //hash方法:获取位置的hash值
	    List<String> hash = opsForGeo.hash(key, "荆州","宜昌");
	    hash.forEach(System.out::println);
	    
	}

参考: https://www.it610.com/article/1288713480500748288.html.

在 Spring Boot 中使用 RedisGEO 功能来计算两个地理位置之间的距离,可以通过 `Spring Data Redis` 提供的 API 实现。RedisGEO 功能基于 GeoHash 技术,支持存储地理位置信息并计算两个坐标点之间的距离。 要实现这一功能,需要以下几个步骤: ### 配置 Redis 依赖 在 `pom.xml` 文件中添加 `spring-boot-starter-data-redis` 和 `lettuce` 客户端依赖,确保能够使用 RedisGEO 功能: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </dependency> ``` ### 使用 RedisTemplate 操作 GEO 在 Spring Boot 中,`RedisTemplate` 提供了对 Redis GEO 命令的支持。可以通过 `opsForGeo()` 方法获取 `GeoOperations` 实例,用于执行 GEOADD、GEODIST 等命令。 以下是一个示例代码,展示如何添加地理位置并计算两点之间的距离: ```java import org.springframework.data.geo.*; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; @Service public class GeoService { @Resource private RedisTemplate<String, String> redisTemplate; public void addLocation(String key, double longitude, double latitude, String member) { redisTemplate.opsForGeo().add(key, new Point(longitude, latitude), member); } public Double calculateDistance(String key, String member1, String member2, Metric metric) { return redisTemplate.opsForGeo().distance(key, member1, member2, metric); } } ``` ### 控制器示例 为了通过 HTTP 接口测试功能,可以创建一个简单的控制器类: ```java import org.springframework.web.bind.annotation.*; import org.springframework.data.geo.Metric; import org.springframework.data.geo.Metrics; @RestController @RequestMapping("/geo") public class GeoController { private final GeoService geoService; public GeoController(GeoService geoService) { this.geoService = geoService; } @PostMapping("/add") public String addLocation(@RequestParam String key, @RequestParam double longitude, @RequestParam double latitude, @RequestParam String member) { geoService.addLocation(key, longitude, latitude, member); return "Location added"; } @GetMapping("/distance") public Double getDistance(@RequestParam String key, @RequestParam String member1, @RequestParam String member2, @RequestParam String unit) { Metric metric = switch (unit.toLowerCase()) { case "km" -> Metrics.KILOMETERS; case "mi" -> Metrics.MILES; case "m" -> Metrics.METERS; case "ft" -> Metrics.FEET; default -> throw new IllegalArgumentException("Unknown unit: " + unit); }; return geoService.calculateDistance(key, member1, member2, metric); } } ``` ### 示例请求 - **添加位置**: ``` POST /geo/add?key=locations&longitude=116.405285&latitude=39.904989&member=Beijing ``` - **计算距离**: ``` GET /geo/distance?key=locations&member1=Beijing&member2=Shanghai&unit=km ``` 通过上述方式,可以在 Spring Boot 应用中使用 RedisGEO 功能来存储地理位置信息并计算两点之间的距离。RedisGEO 功能基于经纬度存储,并支持多种单位(如公里、米、英里、英尺)的距离计算[^1]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值