Java Redis GEO 经纬度附近人
背景
要在小程序中实现一个搜索附近充电桩充电的业务,从国网电力API获取充电站信息和充电桩信息,这里不涉及充电桩,只说充电站,充电站有自己的经纬度,用户小程序获取自己当前位置的经纬度,根据当前自己的经纬度推荐附近的充电站。
使用Redis的GEO命令来实现该功能。
Redis GEO
-
Redis GEO 功能介绍
Redis GEO 是 Redis 3.2 版本新增的功能,主要用于存储和操作地理位置信息。它提供了一系列命令来处理地理空间数据,包括添加坐标、获取坐标、计算距离、查询指定范围内的地理位置集合等。 -
Redis GEO 命令
Redis GEO 提供的命令主要有以下几种:- geoadd: 添加地理位置的坐标。
- geopos: 获取地理位置的坐标。
- geodist: 计算两个位置之间的距离。
- georadius: 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
- georadiusbymember: 根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
- geohash: 返回一个或多个位置对象的 geohash 值。
-
Redis GEO 使用示例
以下是使用 Redis GEO 的一些示例:# 添加地理位置坐标 GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" # 获取地理位置坐标 GEOPOS Sicily Palermo Catania # 计算两个位置之间的距离 GEODIST Sicily Palermo Catania # 获取指定范围内的地理位置集合 GEORADIUS Sicily 15 37 200 km WITHDIST # 根据位置集合里的地点获取指定范围内的地理位置集合 GEORADIUSBYMEMBER Sicily Agrigento 100 km # 获取位置对象的 geohash 值 GEOHASH Sicily Palermo Catania
-
Redis GEO 实战应用
Redis GEO 可以应用于多种场景,例如:- 位置数据存储: 可以将地理位置信息存储在 Redis 中,以便快速检索和操作。
- 附近的搜索: 可以根据用户的位置找到附近的商店、餐馆或其他地点。
- 距离计算: 可以计算两个地点之间的距离,用于配送费估算、路线规划等。
-
注意事项
在使用 Redis GEO 时,需要注意以下几点:-
数据结构:Redis GEO 基于 Sorted Set 数据结构实现,因此可以利用 Sorted Set 的特性进行操作。
-
空间索引:Redis GEO 使用 geohash 空间索引来存储和查询地理位置数据。
-
性能考虑:虽然 Redis 是内存数据库,但在处理大量地理位置数据时,仍需考虑内存使用和性能优化。
-
Redis GEO 功能为开发者提供了一个高效、易用的地理空间数据处理工具,可以广泛应用于需要地理位置信息的各种应用场景中。# Java 示例
代码示例
添加地理位置坐标
-
命令
语法 geoadd key 经度 纬度 成员标识
GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
-
Java代码
新建一个CsmStation对象,属性包括站点名称、站点地址、站点联系电话、经纬度,下面java示例均使用该对象
public class CsmStation extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键 */
private Long id;
/** 站点名称 */
@Excel(name = "站点名称")
private String name;
/** 站点位置 */
@Excel(name = "站点位置")
private String address;
/** 联系电话 */
@Excel(name = "联系电话")
private String phone;
/** 纬度 */
@Excel(name = "纬度")
private Double latitude;
/** 经度 */
@Excel(name = "经度")
private Double longitude;
//....省略get set
}
public class Service implements IService{
private Logger log = LoggerFactory.getLogger(CsmStationServiceImpl.class);
@Autowired
private CsmStationMapper csmStationMapper;
@Autowired
private RedisTemplate redisTemplate;
private static final String KEY = "STATION:GEO:";
/**
* 项目启动时将数据加载到redis中
* @return
*/
@PostConstruct
public void loadStationInfo(){
//创建一个Map,key是站点id,value是站点经纬度,point对象就是下图中的
HashMap<Long, Point> pointHashMap = new HashMap<Long, Point>();
pointHashMap.put(csmStation.getId(), new Point(csmStation.getLongitude(), csmStation.getLatitude()));
//redis添加坐标
redisTemplate.opsForGeo().add( KEY, pointHashMap);
}
}
这个Point
就是上面代码示例中Map的value,表示一个坐标点,属性就是横纵坐标也就是经纬度。
效果如下图,这里的Member就是成员的意思,也就是上面我们Map的key。上面示例中只添加了一条数据,这里截图是添加多条数据的效果
获取地理位置坐标
- 命令 geopos key 成员标识1 成员标识2…
GEOPOS Sicily Palermo Catania
- Java代码
//# 获取地理位置坐标
List position = redisTemplate.opsForGeo().position(KEY, 1L);
for (Object o : position) {
System.out.println("根据key和members获取地理位置信息:"+JSON.toJSONString(o));
}
- 打印结果
根据key和members获取地理位置信息:{“x”:106.22694879770279,“y”:38.48265245683988}
计算两个位置之间的距离
- 命令 geodist key 成员标识1 成员标识2
GEODIST Sicily Palermo Catani
- Java代码
//# 计算两个位置之间的距离
Distance distance = redisTemplate.opsForGeo().distance(KEY, 1L, 2L);
System.out.println("获取id为 1 和 2 的两个站点间的距离"+distance.getValue()+distance.getUnit() );
- 打印结果
获取id为 1 和 2 的两个站点间的距离1888.8776m
获取指定范围内的地理位置集合
- 命令
GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
-
key
-
longitude 经度
-
latitude 纬度
-
radius 半径
-
unit 单位(米 千米等)
-
withdist 可选参数,表示返回结果时,除了地理位置的名称(或 ID)外,还会返回每个点到圆心的距离。距离的单位与 radius 参数中指定的单位一致(这里是 km)。
-
withcoord 如果加上 WITHCOORD 参数,结果中还会包含每个点的经纬度坐标。示例
GEORADIUS Sicily 15 37 200 km WITHCOORD WITHDIST
表示返回经纬度15 37点半径为200km范围内的所有点,并且包含每个点到中心的距离以及每个点自己的经纬度 -
withhash
-
count 限制返回结果的数量。例如,COUNT 1 表示只返回距离圆心最近的一个点。
-
asc|desc 按照距离升序(ASC)或降序(DESC)排序结果。
-
store key 将查询结果保存到指定的 Redis 键中。
-
storedist key
-
- Java代码
//# 获取指定范围内的地理位置集合
//从KEY中获取指定圆心为point的半径是10 km的
GeoResults radius = redisTemplate.opsForGeo().radius(KEY, new Circle(new Point( 106.347075,38.548675), new Distance(10, Metrics.KILOMETERS)));
List content = radius.getContent();
for (Object o : content) {
System.out.println("获取 宁夏回族自治区银川市贺兰县习岗镇太阳城 距离10km内的站点"+JSON.toJSONString(o));
}
- 打印结果
获取 宁夏回族自治区银川市贺兰县习岗镇太阳城 距离10km内的站点{“content”:{“name”:20},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 宁夏回族自治区银川市贺兰县习岗镇太阳城 距离10km内的站点{“content”:{“name”:24},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 宁夏回族自治区银川市贺兰县习岗镇太阳城 距离10km内的站点{“content”:{“name”:17},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 宁夏回族自治区银川市贺兰县习岗镇太阳城 距离10km内的站点{“content”:{“name”:14},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
根据成员获取距离成员半径内的集合
- 命令
GEORADIUSBYMEMBER Sicily Agrigento 100 km
- 代码
//# 根据位置集合里的地点获取指定范围内的地理位置集合
GeoResults geoResults = redisTemplate.opsForGeo().geoRadiusByMember(KEY, 1l, new Distance(10, Metrics.KILOMETERS));
List content = geoResults.getContent();
for (Object o : content) {
System.out.println("获取 1的 距离10km内的站点"+JSON.toJSONString(o));
}
- 结果
获取 1的 距离10km内的站点{“content”:{“name”:25},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:28},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:13},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:11},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:22},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:23},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:5},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:2},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:7},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:1},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:30},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:9},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
获取 1的 距离10km内的站点{“content”:{“name”:26},“distance”:{“metric”:“NEUTRAL”,“normalizedValue”:0.0,“unit”:“”,“value”:0.0}}
代码示例
@Service
public class CsmStationServiceImpl implements ICsmStationService
{
private Logger log = LoggerFactory.getLogger(CsmStationServiceImpl.class);
@Autowired
private CsmStationMapper csmStationMapper;
@Autowired
private RedisTemplate redisTemplate;
private static final String KEY = "STATION:GEO:";
/**
* 项目启动时将数据加载到内存中
* @return
*/
@PostConstruct
public void loadStationInfo(){
//根据区域信息找到站点信息 将站点信息存入redis
List<CsmStation> csmStationList = csmStationMapper.selectCsmStationList(new CsmStation());
for (CsmStation csmStation : csmStationList) {
log.info("将站点信息加入redis");
redisTemplate.opsForValue().set("station:"+csmStation.getId(),csmStation);
//# 添加地理位置坐标
//GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
HashMap<Long, Point> pointHashMap = new HashMap<Long, Point>();
pointHashMap.put(csmStation.getId(), new Point(csmStation.getLongitude(), csmStation.getLatitude()));
redisTemplate.opsForGeo().add( KEY, pointHashMap);
}
}
@Override
public void getPosition(){
//# 获取地理位置坐标
//GEOPOS Sicily Palermo Catania
List position = redisTemplate.opsForGeo().position(KEY, 1L);
for (Object o : position) {
System.out.println("根据key和members获取地理位置信息:"+JSON.toJSONString(o));
}
}
@Override
public void palermo(){
//# 计算两个位置之间的距离
//GEODIST Sicily Palermo Catani
Distance distance = redisTemplate.opsForGeo().distance(KEY, 1L, 2L);
System.out.println("获取id为 1 和 2 的两个站点间的距离"+distance.getValue()+distance.getUnit() );
}
@Override
public void range(){
//# 获取指定范围内的地理位置集合
//GEORADIUS Sicily 15 37 200 km WITHDIST
//宁夏回族自治区银川市贺兰县习岗镇太阳城
GeoResults radius = redisTemplate.opsForGeo().radius(KEY, new Circle(new Point( 106.347075,38.548675), new Distance(10, Metrics.KILOMETERS)));
List content = radius.getContent();
for (Object o : content) {
System.out.println("获取 宁夏回族自治区银川市贺兰县习岗镇太阳城 距离10km内的站点"+JSON.toJSONString(o));
}
}
@Override
public void limitRange(){
//# 根据位置集合里的地点获取指定范围内的地理位置集合
//GEORADIUSBYMEMBER Sicily Agrigento 100 km
GeoResults geoResults = redisTemplate.opsForGeo().geoRadiusByMember(KEY, 1l, new Distance(10, Metrics.KILOMETERS));
List content = geoResults.getContent();
for (Object o : content) {
System.out.println("获取 1的 距离10km内的站点"+JSON.toJSONString(o));
}
}
@Override
public void geohash(){
//# 获取位置对象的 geohash 值
//GEOHASH Sicily Palermo Catania
List hash = redisTemplate.opsForGeo().hash(KEY, 1L, 2L, 3L, 4L);
for (Object o : hash) {
System.out.println("获取 1 2 3 4的 geohas值"+JSON.toJSONString(o));
}
}
}
获取 1 2 3 4的 geohas值"wqgdcm81eq0"
获取 1 2 3 4的 geohas值"wqgdbzskm20"
获取 1 2 3 4的 geohas值"wqg6znp2kw0"
获取 1 2 3 4的 geohas值"wqgdfjvz9d0"
SQL语句
CREATE TABLE csm_station (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
address VARCHAR(255) NOT NULL,
phone VARCHAR(50),
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL
);
INSERT INTO csm_station (name, address, phone, latitude, longitude) VALUES
('春宇智充充电站(宁夏健源新能源充电站)', '宁夏回族自治区银川市金凤区富安西巷联信机动车检测站旁', NULL, 38.482652, 106.226949),
('星星充电充电站(银川城投文化城地下站)', '银川市金凤区弘文巷银川文化城', '4008280768', 38.494078, 106.210897),
('特来电充电站(银川建投地产充电桩)', '宁夏回族自治区银川市西夏区北京西路街道黄河西路353号建工大厦(育林巷)', '4001300001', 38.485184, 106.138087),
('驴充充充电站(银川银房充电站(唐徕...)', '银川市-兴庆区-新民巷19号附近唐徕小区(东区)', NULL, 38.485032, 106.267729),
('驴充充充电站(银川银房充电站(芳馨...)', '银川市-金凤区-北京中路854号(新华联广场对面)芳馨园芳草园住宅小区', NULL, 38.490315, 106.183967),
('驴充充充电站(满春汽车充电桩)', '宁夏回族自治区银川市兴庆区上海东路辅路满春家园内,诚鑫不锈钢铝塑门窗附近39米', NULL, 38.484571, 106.312596),
('春尚充电桩充电站(春宇智充宁夏健源...)', '宁夏回族自治区银川市金凤区北京中路街道黄河东路667号天猫养车(星宝行紫荆花店)', NULL, 38.481832, 106.226824),
('民生二号院充电桩', '宁夏回族自治区银川市贺兰县习岗镇民生兴庆府2号院', NULL, 38.51584, 106.295337),
('润诚达充电站(兴洲花园2台慢充桩)', '银川市西夏区西花园北巷建发兴洲花园', NULL, 38.514172, 106.172882),
('灵武电商创业园-充电桩', '宁夏回族自治区银川市灵武市城区街道灵武电商创业园停车场', NULL, 38.087111, 106.319811),
('新能源汽车充电桩', '银川市金凤区塔渠街宝湖锦都东侧约50米', NULL, 38.448867, 106.257268),
('大汽充电桩', '宁夏回族自治区银川市兴庆区凤凰北街街道新材韶光SOHO公寓', NULL, 38.49351, 106.265316),
('云快充充电站(南泊湾地下共享充电桩)', '银川市金凤区正源南街与六盘山中路交叉口西南角南泊湾25号楼D035号车位', NULL, 38.44004, 106.240542),
('驴充充充电站(贺兰新天地汽车充电桩)', '宁夏回族自治区银川市贺兰县湖西巷贺兰新天地内,新天地9号楼东南94米', NULL, 38.57569, 106.360419),
('国家电网充电站(银川市工农巷17号...)', '银川市兴庆区工农巷17号(鼓楼西南角)', '(0951)4965138', 38.470817, 106.285324),
('云快充充电站(金色港湾充电桩)', '临河镇金色港湾停车场', NULL, 38.28575, 106.346373),
('驴充充充电站(贺兰府汽车充电桩)', '银川市贺兰县碧园街贺兰碧桂园东北侧约190米', NULL, 38.576596, 106.34616),
('快电充电站(民生二号院充电桩)', '银川市民生兴庆府二号院', NULL, 38.515973, 106.295246),
('公牛充电桩(丽景北街)', '银川市兴庆区丽景北街银川恒大御景', NULL, 38.488344, 106.311272),
('汽车充电桩', '宁夏回族自治区银川市贺兰县习岗镇太阳城', NULL, 38.548675, 106.347075),
('新能源二手车公牛充电桩', '宁夏回族自治区银川市兴庆区丽景街街道银川恒大御景', NULL, 38.48837, 106.311243),
('电动汽车充电桩(金域蓝湾一期站)', '宁夏回族自治区银川市金凤区长城中路街道丽银路74号金域蓝湾一期北面停车泊位', NULL, 38.436306, 106.271138),
('国桩智充速联电充电站(天汇里站)', '宁夏回族自治区银川市金凤区福州南街世茂倾城东侧约110米', '13995382200', 38.472458, 106.197333),
('驴充充充电站(贺兰印象汽车充电桩)', '银川市贺兰县恒安北街银大·荷兰印象', '(0797)966999', 38.568632, 106.336477),
('宁夏跃强新能源充电桩', '宁夏回族自治区银川市金凤区水乡路iBi育成中心2期北停车场', '18509513329', 38.447939, 106.227098),
('国家电网充电站(金凤区万寿路177...)', '银川市金凤区万寿路177号市民大厅停车场北边东侧', '(0951)4965138', 38.53381, 106.247048),
('公牛充电桩新能源二手车(丽景北街店)', '宁夏回族自治区银川市兴庆区新苗巷72号', NULL, 38.487239, 106.310919),
('驴充充充电站(凯旋帝景汽车充电桩)', '宁夏回族自治区银川市金凤区湖畔路凯旋帝景内', '(0797)966999', 38.449343, 106.234883),
('国家电网充电站(银川市长城东路27...)', '宁夏回族自治区银川市兴庆区长城东路277号能源小区门口', '13209517297', 38.463379, 106.277029),
('科大优享汽车充电站(银川紫荆花商务...)', '银川市金凤区紫荆花商务广场物业门口', '15385895960', 38.486213, 106.232889);