作者:施自扬
微信号:sszzyy123aabbcc
SpringBoot
Redis(version>=3.2)
GEOADD [key] [longitude] [latitude] [member]
GEOADD user 116.48105 39.996794 zhangsan
GEOADD user 116.514203 39.905409 lisi
GEOADD user 116.489033 40.007669 wangwu
GEOADD user 116.562108 39.787602 sunliu
GEOADD user 116.334255 40.027400 zhaoqi
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST]
[WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
georadiusbymember user lisi 100 km asc count 5
georadiusbymember user lisi 100 km asc count 5 storedist lisilimitkey
//首先删除本身元素
zrem lisilimitkey lisi
//分页查找元素(在此以:查找第1页,每页数量是3为例)
zrange lisilimitkey 0 2 withscores
https://github.com/shiziyang666/public/tree/master/demo
package com.demo;
import com.demo.service.PeopleNearbyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Autowired
private PeopleNearbyService peopleNearbyService;
@Override
public void run(String... args) throws Exception {
//添加地理位置
peopleNearbyService.postUserAddress("city", 116.405285, 39.904989, "北京");
peopleNearbyService.postUserAddress("city", 121.47, 31.23, "上海");
peopleNearbyService.postUserAddress("city", 113.27, 23.13, "广州");
peopleNearbyService.postUserAddress("city", 43.86, 10.40, "深圳");
//获取附近的人地理位置
peopleNearbyService.listNearbyUser("city", "深圳", 8000, 4);
//分页查询附近的人
peopleNearbyService.listNearbyUserLimit(1, 2, "city", "深圳", "8000", "km", "asc", "shenzhennewkey");
}
}
package com.demo.service;
/***
* shiziyang
* 附近的人
*/
public interface PeopleNearbyService {
/***
* 添加地理位置
* @param key rediskey
* @param precision 经度
* @param dimension 维度
* @param name 位置名称
*/
void postUserAddress(String key, double precision, double dimension, String name);
/***
* 获取最近附近的人地理位置
* @param key rediskey
* @param name 位置名称
* @param distance 范围km数
* @param count 获取最近几条
*/
void listNearbyUser(String key, String name, Integer distance, Integer count);
/***
* 分页获取最近附近的人地理位置
* @param pageIndex 第几页
* @param pageSize 每页条数
* @param key redis key "city",
* @param name 位置名称 "深圳",
* @param distance 距离 "8000",
* @param distanceUnit 距离单位 "km",
* @param sort 排序 "asc",
* @param newKey 新的redis key "shenzhennewkey"
*/
void listNearbyUserLimit(Integer pageIndex, Integer pageSize, String key, String name, String distance, String distanceUnit, String sort, String newKey);
}
package com.demo.service.impl;
import com.demo.entity.MemberGpsEntity;
import com.demo.service.PeopleNearbyService;
import com.demo.util.GeoHashUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* shiziyang
*/
@Service
public class PeopleNearbyServiceImpl implements PeopleNearbyService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private GeoHashUtil geoHashUtil;
@Override
public void postUserAddress(String key, double precision, double dimension, String name) {
//对应redis原生命令:GEOADD user 116.48105 39.996794 zhangsan
redisTemplate.opsForGeo().add(key, new Point(precision, dimension), name);
geoHashUtil.redisGeoAdd(key, precision, dimension, name);
}
@Override
public void listNearbyUser(String key, String name, Integer distance, Integer count) {
GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = geoHashUtil.geoNearByPlace(key, name, distance, count);
System.out.println(geoResults);
}
@Override
public void listNearbyUserLimit(Integer pageIndex, Integer pageSize, String key, String name, String distance, String distanceUnit, String sort, String newKey) {
//将附近的人存储到一个key里
Object execute = execute("return redis.call('georadiusbymember',KEYS[1],KEYS[2],KEYS[3],KEYS[4],KEYS[5],'storedist',KEYS[6])", key, name, distance, distanceUnit, sort, newKey);
//给新key设置失效时间
redisTemplate.expire(newKey, 6, TimeUnit.HOURS);
//删除自己
redisTemplate.opsForGeo().remove(newKey, name);
//开始条数
Integer startPage = (pageIndex - 1) * pageSize;
//结束条数
Integer endPage = pageIndex * pageSize - 1;
//获取分页信息
Set<ZSetOperations.TypedTuple<Object>> aaa =
redisTemplate.opsForZSet().rangeWithScores(newKey, startPage, endPage);
//处理返回值
List<MemberGpsEntity> memberGpsEntityList = aaa.stream().map(aa -> {
MemberGpsEntity memberGpsEntity = new MemberGpsEntity();
//名称
memberGpsEntity.setName(aa.getValue().toString());
//距离
memberGpsEntity.setDistance(aa.getScore());
return memberGpsEntity;
}).collect(Collectors.toList());
//打印结果
System.out.println(memberGpsEntityList);
}
/**
* 执行lua脚本
*
* @param text lua 脚本
* @param str lua脚本的参数
*/
private Object execute(String text, String... str) {
//参数处理
List<String> params = Arrays.asList(str);
DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
//设置返回值
defaultRedisScript.setResultType(Long.class);
//设置脚本
defaultRedisScript.setScriptText(text);
//执行命令
Object execute = redisTemplate.execute(defaultRedisScript, params);
return execute;
}
}
package com.demo.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class GeoHashUtil {
@Autowired
private RedisTemplate redisTemplate;
/***
* 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。
* @param key redis的key
* @param precision 经度
* @param dimension 纬度
* @param name 名称
* @return
*/
public Long redisGeoAdd(String key, double precision, double dimension, String name) {
// Long addedNum = redisTemplate.opsForGeo().add("city", new Point(116.405285, 39.904989), "北京");
// Long addedNum = redisTemplate.opsForGeo().add("city", new Point(121.47, 31.23), "上海");
// Long addedNum = redisTemplate.opsForGeo().add("city", new Point(113.27, 23.13), "广州");
Long addedNum = redisTemplate.opsForGeo().add(key, new Point(precision, dimension), name);//params: key, Point(经度, 纬度), 地方名称
return addedNum;
}
/***
* 从key里返回所有给定位置元素的位置(经度和纬度)。
* @param key redis的key
* @param nameList 名称的集合
*/
public List<Point> redisGeoGet(String key, List<String> nameList) {
List<Point> points = redisTemplate.opsForGeo().position(key, nameList);//params: key, 地方名称...
return points;
}
/***
* 返回两个给定位置之间的距离。
* @param key redis的key
* @param name1 地方名称1
* @param name2 地方名称2
* @return
*/
public Distance geoDist(String key, String name1, String name2) {
Distance distance = redisTemplate.opsForGeo()
.distance(key, name1, name2, RedisGeoCommands.DistanceUnit.KILOMETERS);//params: key, 地方名称1, 地方名称2, 距离单位
return distance;
}
/***
* 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素,并给出所有位置元素与中心的平均距离。
* @param key redis的key
* @param precision 经度
* @param dimension 纬度
* @param distance 距离
* @param count 人数
* @return
*/
public GeoResults<RedisGeoCommands.GeoLocation<String>> redisNearByXY(String key, double precision, double dimension, Integer distance, Integer count) {
Circle circle = new Circle(new Point(precision, dimension), new Distance(distance, Metrics.KILOMETERS));//Point(经度, 纬度) Distance(距离量, 距离单位)
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(count);
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius(key, circle, args);//params: key, Circle, GeoRadiusCommandArgs
return results;
}
/***
* 以给定的城市为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素,并给出所有位置元素与中心的平均距离。
* @param key redis的key
* @param name 名称
* @param distance 距离
* @param count 人数
* @return
*/
public GeoResults<RedisGeoCommands.GeoLocation<String>> geoNearByPlace(String key, String name, Integer distance, Integer count) {
Distance distances = new Distance(distance, Metrics.KILOMETERS);//params: 距离量, 距离单位
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(count);
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius(key, name, distances, args);//params: key, 地方名称, Circle, GeoRadiusCommandArgs
return results;
}
/***
* 返回一个或多个位置元素的 Geohash 表示
* @param key redis的key
* @param nameList 名称的集合
*/
public List<String> geoHash(String key, List<String> nameList) {
List<String> results = redisTemplate.opsForGeo().hash(key, nameList);//params: key, 地方名称...
return results;
}
}
GeoHashUtil的使用详情请查看:
https://blog.csdn.net/weixin_41677422/article/details/108260110
分页实现思路:将geo集合中的数据按距离由近到远筛选好后,通过storedist放入一个新的Zset集合,然后通过zset的语法实现分页。