WITHDIST
:在返回位置对象的同时,将位置对象与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致。WITHCOORD
:将位置对象的经度和维度也一并返回。WITHHASH
:以 52 位有符号整数的形式,返回位置对象经过原始 geohash 编码的有序集合分值。这个选项主要用于底层应用或者调试,实际中的作用并不大。ASC | DESC
:从近到远返回位置对象元素 | 从远到近返回位置对象元素。COUNT count
:选取前N个匹配位置对象元素。(不设置则返回所有元素)STORE key
:将返回结果的地理位置信息保存到指定key。STORedisT key
:将返回结果离中心点的距离保存到指定key。例如下边命令:获取当前位置周边500米内的所有饭店。
GEORADIUS hotel 119.98866180732716 30.27465803229662 500 m WITHCOORD
复制代码
Redis
内部使用有序集合(zset
)保存用户的位置信息,zset
中每个元素都是一个带位置的对象,元素的score
值为通过经、纬度计算出的52位geohash
值。
2、利弊分析
redis
实现附近的人
效率比较高,集成也比较简单,而且还支持对距离排序。不过,结果存在一定的误差,要想让结果更加精确,还需要手动将用户中心位置与其他用户位置计算距离后,再一次进行筛选。
3、实现
以下就是Java
redis
实现版本,代码非常的简洁。
@Autowired
private RedisTemplate redisTemplate;
//GEO相关命令用到的KEY
private final static String KEY = "user_info";
public boolean save(User user) {
Long flag = redisTemplate.opsForGeo().add(KEY, new RedisGeoCommands.GeoLocation<>(
user.getName(),
new Point(user.getLongitude(), user.getLatitude()))
);
return flag != null && flag > 0;
}
/**
* 根据当前位置获取附近指定范围内的用户
* @param distance 指定范围 单位km ,可根据{@link org.springframework.data.geo.Metrics} 进行设置
* @param userLng 用户经度
* @param userLat 用户纬度
* @return
*/
public String nearBySearch(double distance, double userLng, double userLat) {
List users = new ArrayList<>();
// 1.GEORADIUS获取附近范围内的信息
GeoResults> reslut =
redisTemplate.opsForGeo().radius(KEY,
new Circle(new Point(userLng, userLat), new Distance(distance, Metrics.KILOMETERS)),
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance()
.includeCoordinates().sortAscending());
//2.收集信息,存入list
List>> content = reslut.getContent();
//3.过滤掉超过距离的数据
content.forEach(a-> users.add(
new User().setDistance(a.getDistance().getValue())
.setLatitude(a.getContent().getPoint().getX())
.setLongitude(a.getContent().getPoint().getY())));
return JSON.toJSONString(users);
}
复制代码
六、MongoDB + 2d索引
1、设计思路
MongoDB
实现附近的人,主要是通过它的两种地理空间索引 2dsphere
和 2d
。 两种索引的底层依然是基于Geohash
来进行构建的。但与国际通用的Geohash
还有一些不同,具体参考官方文档。
2dsphere
索引仅支持球形表面的几何形状查询。
2d
索引支持平面几何形状和一些球形查询。虽然2d
索引支持某些球形查询,但 2d
索引对这些球形查询时,可能会出错。所以球形查询尽量选择 2dsphere
索引。
尽管两种索引的方式不同,但只要坐标跨度不太大,这两个索引计算出的距离相差几乎可以忽略不计。
2、实现
首先插入一批位置数据到MongoDB
, collection
为起名 hotel
,相当于MySQL
的表名。两个字段name
名称,location
为经、纬度数据对。
db.hotel.insertMany([
{'name':'hotel1', location:[115.993121,28.676436]},
{'name':'hotel2', location:[116.000093,28.679402]},
{'name':'hotel3', location:[115.999967,28.679743]},
{'name':'hotel4', location:[115.995593,28.681632]},
{'name':'hotel5', location:[115.975543,28.679509]},
{'name':'hotel6', location:[115.968428,28.669368]},
{'name':'hotel7', location:[116.035262,28.677037]},
{'name':'hotel8', location:[116.024770,28.68667]},
{'name':'hotel9', location:[116.002384,28.683865]},
{'name':'hotel10', location:[116.000821,28.68129]},
])
复制代码
接下来我们给 location
字段创建一个2d
索引,索引的精度通过bits
来指定,bits
越大,索引的精度就越高。
db.coll.createIndex({'location':"2d"}, {"bits":11111})
复制代码
用geoNear
命令测试一下, near
当前坐标(经、纬度),spherical
是否计算球面距离,distanceMultiplier
地球半径,单位是米,默认6378137, maxDistance
过滤条件(指定距离内的用户),开启弧度需除distanceMultiplier
,distanceField
计算出的两点间距离,字段别名(随意取名)。
db.hotel.aggregate({
$geoNear:{
near: [115.999567,28.681813], // 当前坐标
spherical: true, // 计算球面距离
distanceMultiplier: 6378137, // 地球半径,单位是米,那么的除的记录也是米
maxDistance: 2000/6378137, // 过滤条件2000米内,需要弧度
distanceField: "distance" // 距离字段别名
}
})
复制代码
看到结果中有符合条件的数据,还多出一个字段distance
刚才设置的别名,代表两点间的距离。
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e58"), "name" : "hotel10", "location" : [ 116.000821, 28.68129 ], "distance" : 135.60095397487655 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e51"), "name" : "hotel3", "location" : [ 115.999967, 28.679743 ], "distance" : 233.71915803517447 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e50"), "name" : "hotel2", "location" : [ 116.000093, 28.679402 ], "distance" : 273.26317035334176 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e57"), "name" : "hotel9", "location" : [ 116.002384, 28.683865 ], "distance" : 357.5791936927476 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e52"), "name" : "hotel4", "location" : [ 115.995593, 28.681632 ], "distance" : 388.62555058249967 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e4f"), "name" : "hotel1", "location" : [ 115.993121, 28.676436 ], "distance" : 868.6740526419927 }
复制代码
作者:程序员内点事
链接:https://juejin.cn/post/6844904129853128717
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。