文章目录
- 前言
- redis geospatial
- 如何从地图上获取经纬度
- springboot 的相关方法调用
- 准备redis服务器
- 引用的依赖
- 预设位置的key
- GEOADD 添加位置
- GEORADIUS 获取指定经纬度附件的停车场(deprecated)
- GEORADIUS 获取指定成员附件的停车场(deprecated)
- GEOSEARCH 搜索指定经纬度附件的停车场
- GEOSEARCH 搜索指定成员附件的停车场
- GEOPOS获取指定成员经纬度
- GEODIST获取两点之间的距离
- GEOHASH获取坐标的hash
- ZREM 移除位置
- 数学球面计算两点距离
前言
基于redis geospatial的应用比较广泛,比如需要获取附件5公里的停车场。
官方文档:https://redis.io/docs/data-types/geospatial/
代码已分享至Gitee:https://gitee.com/lengcz/redisgeo
redis geospatial
用于地理位置服务的计算,它能做哪些?
- 我周边的共享单车的信息,可以按距离输出
- 两坐标、车辆之间的距离
如何从地图上获取经纬度
高德地图:
https://lbs.amap.com/demo/javascript-api/example/map/click-to-get-lnglat/
百度地图:
https://api.map.baidu.com/lbsapi/getpoint/index.html
springboot 的相关方法调用
准备redis服务器
需要安装redis 服务器
引用的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
注意本demo中的springboot版本为2.7.15,如果你的版本较低,可能部分GEOSEARCH 的API不可用。
如果遇到demo中的示例出现报红,请考虑是否为依赖版本过低。
预设位置的key
private String positionKey = "parking";
GEOADD 添加位置
官方文档: https://redis.io/commands/geoadd/
GEOADD key [NX | XX] [CH] longitude latitude member [longitude
latitude member ...]
注意经纬度需要在范围内
- 经度 -180到180
- 纬度 -85.05112878到85.05112878
如果你添加的经纬度超出范围会报错
XX: 仅更新已存在的元素。永远不要添加元素。
NX:不要更新已经存在的元素。始终添加新元素。
CH:将返回值从添加的新元素数量修改为更改的元素总数(CH是changed的缩写)。已更改的图元是添加的新元素和已更新坐标的现有图元。因此,命令行中指定的分数与过去相同的元素将不被计算在内。注意:通常情况下,GEOADD的返回值只计算添加的新元素数量。
@Test
void geoAdd(@Autowired RedisTemplate redisTemplate) {
System.out.println("--------添加位置-----");
GeoOperations geoOperations = redisTemplate.opsForGeo();
{
Long addedNum = geoOperations
.add(positionKey, new Point(-122.27652, 37.805186), "10001:市图书馆");
System.out.println(addedNum);
}
{
Long addedNum = geoOperations
.add(positionKey, new Point(-122.2674626, 37.8062344), "10002:百货大楼");
System.out.println(addedNum);
}
{
Long addedNum = geoOperations
.add(positionKey, new Point(-122.2469854, 37.8104049), "10003:科学中心");
System.out.println(addedNum);
}
{
Long addedNum = geoOperations
.add(positionKey, new Point(-122.2625112, 37.793513), "10004:博物馆");
System.out.println(addedNum);
}
System.out.println("--------添加位置END-----");
}
GEORADIUS 获取指定经纬度附件的停车场(deprecated)
官方文档: https://redis.io/commands/georadius/
从Redis版本6.2.0开始,此命令被视为已弃用。
在迁移或编写新代码时,可以用带有BYRADIUS参数的GEOSEARCH和GEOSEARCHSTORE替换它。
GEORADIUS key longitude latitude radius <M | KM | FT | MI>
[WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC | DESC]
[STORE key | STOREDIST key]
单位
- m 米
- km 千米
- mi 英里
- ft 英尺
@Test
void geoRadius(@Autowired RedisTemplate redisTemplate) {
// 指定经纬度附近5公里的停车场
Circle circle = new Circle(new Point(-122.2612767, 37.793684), RedisGeoCommands.DistanceUnit.KILOMETERS.getMultiplier());
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().includeDistance().sortAscending().limit(5);
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius(positionKey, circle, args);
System.out.println(results);
for (GeoResult<RedisGeoCommands.GeoLocation<String>> g : results) {
System.out.println(g);
String addr = g.getContent().getName();
System.out.println("addr:" + addr + ",distance:" + g.getDistance().getValue());
}
}
运行结果
GeoResult [content: RedisGeoCommands.GeoLocation(name=10004:博物馆, point=Point [x=-122.262509, y=37.793512]), distance: 109.9417 METERS, ]
addr:10004:博物馆,distance:109.9417
GeoResult [content: RedisGeoCommands.GeoLocation(name=10002:百货大楼, point=Point [x=-122.267460, y=37.806234]), distance: 1497.9608 METERS, ]
addr:10002:百货大楼,distance:1497.9608
GeoResult [content: RedisGeoCommands.GeoLocation(name=10001:市图书馆, point=Point [x=-122.276520, y=37.805185]), distance: 1852.3499 METERS, ]
addr:10001:市图书馆,distance:1852.3499
GeoResult [content: RedisGeoCommands.GeoLocation(name=10003:科学中心, point=Point [x=-122.246984, y=37.810404]), distance: 2244.1523 METERS, ]
addr:10003:科学中心,distance:2244.1523
GEORADIUS 获取指定成员附件的停车场(deprecated)
@Test
void geoRadius2(@Autowired RedisTemplate redisTemplate) {
// 指定地址附近5公里的停车场
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().includeDistance().sortAscending().limit(5);
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius(positionKey, "10001:市图书馆", new Distance(5, Metrics.KILOMETERS), args);
System.out.println(results);
for (GeoResult<RedisGeoCommands.GeoLocation<String>> g : results) {
System.out.println(g);
String addr = g.getContent().getName();
System.out.println("addr:" + addr + ",distance:" + g.getDistance().getValue());
}
}
执行结果
GeoResult [content: RedisGeoCommands.GeoLocation(name=10001:市图书馆, point=Point [x=-122.276520, y=37.805185]), distance: 0.0 KILOMETERS, ]
addr:10001:市图书馆,distance:0.0
GeoResult [content: RedisGeoCommands.GeoLocation(name=10002:百货大楼, point=Point [x=-122.267460, y=37.806234]), distance: 0.8047 KILOMETERS, ]
addr:10002:百货大楼,distance:0.8047
GeoResult [content: RedisGeoCommands.GeoLocation(name=10004:博物馆, point=Point [x=-122.262509, y=37.793512]), distance: 1.7894 KILOMETERS, ]
addr:10004:博物馆,distance:1.7894
GeoResult [content: RedisGeoCommands.GeoLocation(name=10003:科学中心, point=Point [x=-122.246984, y=37.810404]), distance: 2.6597 KILOMETERS, ]
addr:10003:科学中心,distance:2.6597
GEOSEARCH 搜索指定经纬度附件的停车场
官方文档: https://redis.io/commands/geosearch/
注意该命令从redis 6.0.2开始。
GEOSEARCH key <FROMMEMBER member | FROMLONLAT longitude latitude>
<BYRADIUS radius <M | KM | FT | MI> | BYBOX width height <M | KM |
FT | MI>> [ASC | DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST]
[WITHHASH]
@Test
void geoSearch(@Autowired RedisTemplate redisTemplate) {
// 指定经纬度附近5公里的停车场
RedisGeoCommands.GeoSearchCommandArgs geoSearchCommandArgs = RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeCoordinates().includeDistance().sortAscending();
GeoResults<RedisGeoCommands.GeoLocation> searchResult = redisTemplate.opsForGeo().search(positionKey, new GeoReference.GeoCoordinateReference(-122.2612767, 37.793684), new Distance(5, Metrics.KILOMETERS), geoSearchCommandArgs);
// System.out.println(searchResult);
List<GeoResult<RedisGeoCommands.GeoLocation>> content = searchResult.getContent();
for (GeoResult<RedisGeoCommands.GeoLocation> g : content) {
// System.out.println(g);
RedisGeoCommands.GeoLocation content1 = g.getContent();
Distance distance = g.getDistance();
System.out.println("content1:" + content1 + ",distance:" + distance);
}
}
执行结果
content1:RedisGeoCommands.GeoLocation(name=10004:博物馆, point=Point [x=-122.262509, y=37.793512]),distance:0.1099 KILOMETERS
content1:RedisGeoCommands.GeoLocation(name=10001:市图书馆, point=Point [x=-122.276520, y=37.805185]),distance:1.8523 KILOMETERS
content1:RedisGeoCommands.GeoLocation(name=10003:科学中心, point=Point [x=-122.246984, y=37.810404]),distance:2.2442 KILOMETERS
GEOSEARCH 搜索指定成员附件的停车场
@Test
void geoSearch2(@Autowired RedisTemplate redisTemplate) {
// 指定成员附近5公里的停车场
RedisGeoCommands.GeoSearchCommandArgs geoSearchCommandArgs = RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeCoordinates().includeDistance().sortAscending();
GeoResults<RedisGeoCommands.GeoLocation> searchResult = redisTemplate.opsForGeo().search(positionKey, new GeoReference.GeoMemberReference<>("10004:博物馆"), new Distance(5, Metrics.KILOMETERS), geoSearchCommandArgs);
// System.out.println(searchResult);
List<GeoResult<RedisGeoCommands.GeoLocation>> content = searchResult.getContent();
for (GeoResult<RedisGeoCommands.GeoLocation> g : content) {
// System.out.println(g);
RedisGeoCommands.GeoLocation content1 = g.getContent();
Distance distance = g.getDistance();
System.out.println("content1:" + content1 + ",distance:" + distance);
}
}
执行结果
content1:RedisGeoCommands.GeoLocation(name=10004:博物馆, point=Point [x=-122.262509, y=37.793512]),distance:0.0 KILOMETERS
content1:RedisGeoCommands.GeoLocation(name=10001:市图书馆, point=Point [x=-122.276520, y=37.805185]),distance:1.7894 KILOMETERS
content1:RedisGeoCommands.GeoLocation(name=10003:科学中心, point=Point [x=-122.246984, y=37.810404]),distance:2.3219 KILOMETERS
GEOPOS获取指定成员经纬度
官方文档 https://redis.io/commands/geopos/
GEOPOS key [member [member ...]]
@Test
void geoGetPoints(@Autowired RedisTemplate redisTemplate) {
List<Point> points = redisTemplate.opsForGeo().position(positionKey, "10003:科学中心", "10004:博物馆");
System.out.println(points);
}
执行结果
[Point [x=-122.246984, y=37.810404], Point [x=-122.262509, y=37.793512]]
GEODIST获取两点之间的距离
官方文档: https://redis.io/commands/geodist/
命令
GEODIST key member1 member2 [M | KM | FT | MI]
单位
- m 米
- km 千米
- mi 英里
- ft 英尺
@Test
public void testDist(@Autowired RedisTemplate redisTemplate) {
Distance distance = redisTemplate.opsForGeo()
.distance(positionKey, "10003:科学中心", "10004:博物馆", RedisGeoCommands.DistanceUnit.KILOMETERS);
System.out.println(distance);
}
执行结果
2.3219 KILOMETERS
GEOHASH获取坐标的hash
官方文档: https://redis.io/commands/geohash/
GEOHASH key [member [member ...]]
这里给定的10005不存在
@Test
public void testGeoHash(@Autowired RedisTemplate redisTemplate) {
List<String> results = redisTemplate.opsForGeo()
.hash(positionKey, "10004:博物馆", "10002:百货大楼", "10005:欢乐海岸");
System.out.println(results);
}
执行结果
[9q9p1b55jj0, 9q9p1drt380, null]
ZREM 移除位置
官方没有GEODEL,需要使用ZREM命令
官方文档: https://redis.io/commands/zrem/
@Test
public void testGeoRemove(@Autowired RedisTemplate redisTemplate) {
Long remove = redisTemplate.opsForGeo().remove(positionKey, "10002:百货大楼");
List<String> results = redisTemplate.opsForGeo()
.hash(positionKey, "10004:博物馆", "10002:百货大楼", "10005:欢乐海岸");
System.out.println(results);
}
执行结果,可以看到10002百货大楼已经被移除了。
[9q9p1b55jj0, null, null]
注意: redis geo没有GEODEL命令,需要使用ZREM命令删除元素
数学球面计算两点距离
/**
* 通过球面计算距离
*/
public class DistanceUtil {
/**
* 赤道半径(m)
*/
public final static double RC = 6378137;
/**
* 极半径(m)
*/
public final static double RJ = 6356725;
/**
* 将角度转化为弧度
*/
public static double radians(double d) {
return d * Math.PI / 180.0;
}
/**
* 根据两点经纬度((longitude and latitude))坐标计算直线距离
* <p>
* S = 2arcsin√sin²(a/2)+cos(lat1)*cos(lat2)*sin²(b/2) ̄*6378137
* <p>
* 1. lng1 lat1 表示A点经纬度,lng2 lat2 表示B点经纬度;<br>
* 2. a=lat1 – lat2 为两点纬度之差 b=lng1 -lng2 为两点经度之差;<br>
* 3. 6378137为地球赤道半径,单位为米;
*
* @param longitude1 点1经度
* @param latitude1 点1纬度
* @param longitude2 点2经度
* @param latitude2 点2纬度
* @return 距离,单位米(M)
* @see <a href=
* "https://zh.wikipedia.org/wiki/%E5%8D%8A%E6%AD%A3%E7%9F%A2%E5%85%AC%E5%BC%8F">
* 半正矢(Haversine)公式</a>
*/
public static double getDistanceFromToLngLat(double longitude1, double latitude1, double longitude2, double latitude2) {
// 将角度转化为弧度
double radLongitude1 = radians(longitude1);
double radLatitude1 = radians(latitude1);
double radLongitude2 = radians(longitude2);
double radLatitude2 = radians(latitude2);
double a = radLatitude1 - radLatitude2;
double b = radLongitude1 - radLongitude2;
return 2 * Math.asin(Math.sqrt(Math.sin(a / 2) * Math.sin(a / 2)
+ Math.cos(radLatitude1) * Math.cos(radLatitude2) * Math.sin(b / 2) * Math.sin(b / 2))) * (RC);
}
/**
* 获取指定角度方向上的经纬度(longitude and latitude)<br>
* 以原始点的经纬度为基点,构建XY二维坐标线,则angle角度则从x轴起算。<br>
* 说明:LBS查找,东西南北四个方向的最远点,构建矩形,在矩形框方位内, 才有可能是该距离方位内的坐标,然后利用距离公式从小范围内找坐标
*
* @param origLongitude 基点经度
* @param origLatitude 基点纬度
* @param distance 距离(m)
* @param angle 角度(0~360)
* @param 经纬度(该角度指定距离的经纬度)
*/
public static Coordinate getCoordinate(double origLongitude, double origLatitude, double distance, double angle) {
double x = distance * Math.sin(angle * Math.PI / 180.0);
double y = distance * Math.cos(angle * Math.PI / 180.0);
double r = RJ + (RC - RJ) * (90.0 - origLongitude) / 90.0;//由于地球不是标准的球体,中间粗,两头细,这里計算平均半徑r
double d = r * Math.cos(origLongitude * Math.PI / 180);
double newLongitude = (y / r + origLongitude * Math.PI / 180.0) * 180.0 / Math.PI;
double newLatitude = (x / d + origLatitude * Math.PI / 180.0) * 180.0 / Math.PI;
return new Coordinate(newLongitude, newLatitude);
}
}
测试用例,用例的经纬度为GEOSEARCH示例中目标到科学中心的距离
@Test
void getDist() {
double longitude1 = -122.2612767;
double latitude1 = 37.793684;
double longitude2 = -122.2469854;
double latitude2 = 37.8104049;
double distance = DistanceUtil.getDistanceFromToLngLat(longitude1, latitude1, longitude2, latitude2);
System.out.println("经纬度(" + longitude1 + "," + latitude1 + ")到(" + longitude2 + "," + latitude2 + ")的距离为: " + distance + " 米");
}
运行测试用例,返回结果与GEO返回的结果基本吻合。
经纬度(-122.2612767,37.793684)到(-122.2469854,37.8104049)的距离为: 2246.057783614703 米