我想实现一项服务,根据用户的地理坐标,可以检测两个用户是否实时位于同一位置。
为了实时和扩展,我似乎应该使用像Redis这样的分布式内存数据存储。我已经研究过使用geohashing,但问题是彼此接近的点可能并不总是共享相同的哈希前缀。因为我有兴趣找到两个用户是否足够接近他们彼此相邻的位置,所以geohashing可能有点过头了。
当然,简单的解决方案就是测试地理坐标对是否在彼此的小距离内。但AFAIK,Redis和其他内存数据采集器没有地理空间索引来支持这种查找。
实施此方法的最佳方法是什么?
答案 0 :(得分:17)
此功能已融入Redis 3.2+。
但对于旧版本,问题仍然存在。我接受了Yin Qiwen的回答并为Node创建了一个模块,你可以通过检查代码来看看它是如何使用Redis的。他的指示是完美的,我能够跟随他们取得好成绩。 https://github.com/arjunmehta/node-georedis
相同的算法本质上是用于本机命令的。
它非常快,并且避免任何类型的交叉/半字形操作。关于尹启文方法最酷的事情(我认为)是算法中计算量最大的部分可以分发给客户端(而不是发生在数据库或服务器上)。
它不是100%精确,并且使用预先配置的距离步长,但对于大多数应用程序,您不需要我想象的精确度。
我也在GIS stack exchange翻译了尹启文的文章。
对不起所有关联。 :P
答案 1 :(得分:15)
通常,这可以通过GeoHash和Redis的排序集来完成。在讨论如何在redis上实现空间索引服务之前,我写了一个设计。
答案 2 :(得分:7)
答案 3 :(得分:5)
我意识到这不能回答你的问题...但我认为这不是正确的工具。
PostgreSQL + PostGIS可以表现得非常好。您可以将PostgreSQL配置为几乎可以运行在内存中的数据库。
PostGIS使用(我认为)rtree索引,因此执行您感兴趣的查找速度非常快。
使用后端触发websocket请求将允许您执行非常实时的操作。您的后端随时收到GPS坐标;执行空间查找;并通过websockets通知适用的客户。
答案 4 :(得分:5)
此主题中其他答案所提到的Redis地理版已集成到Redis since version 3.2中(另请参阅this earlier comment)。
您可以在此处找到新命令(目前处于测试阶段):
答案 5 :(得分:1)
Tarantool数据库将数据保存在内存中,将它们作为事务日志推送到磁盘,具有RTree类型的空间索引(不仅仅是2维)以及对此类索引(包含,重叠,距离)的一些不错操作。
我在商业项目中使用它来存储和查询描述3D空间中对象的记录。
http://tarantool.org/doc/book/box/box_index.html
https://github.com/tarantool/tarantool/wiki/R-tree-index-quick-start-and-usage
标准客户端和示例在Lua中,但是还有一些由数据库作者开发的其他客户端。我成功地在Scala应用程序中使用Java客户端。
数据库也非常快 - 这里与其他数据库进行科学比较(不考虑作为空间数据库的一个方面): http://airccse.org/journal/ijdms/papers/6314ijdms01.pdf
答案 6 :(得分:0)
我想分享Redis地理版的Java代码示例。
public void geoadd(String objectId, BigDecimal latitude, BigDecimal longitude) {
log.info("geoadd(): {} {} {}", objectId, latitude, longitude);
try (Jedis jedis = jedisPool.getResource()) {
if (geoaddSha == null) {
String script = "return redis.call('geoadd','" + GEOSET + "', ARGV[1], ARGV[2], KEYS[1])";
geoaddSha = jedis.scriptLoad(script);
}
log.info("geoaddSha: {}", geoaddSha);
log.info(jedis.evalsha(geoaddSha, 1, objectId, latitude.toString(), longitude.toString()).toString());
}
}
@SuppressWarnings("unchecked")
public List<String> georadius(BigDecimal latitude, BigDecimal longitude, int radius, Unit unit) {
log.info("georadius(): {} {} {} {}", latitude, longitude, radius, unit);
try (Jedis jedis = jedisPool.getResource()) {
if (georadiusSha == null) {
String script = "return redis.call('georadius','" + GEOSET + "', ARGV[1], ARGV[2], ARGV[3], ARGV[4])";
georadiusSha = jedis.scriptLoad(script);
}
log.info("georadiusSha: {}", georadiusSha);
List<String> objectIdList = (List<String>) jedis.evalsha(georadiusSha, 0, latitude.toString(), longitude.toString(), String.valueOf(radius), unit.toString());
log.info("objectIdList: {}", objectIdList);
return objectIdList;
}
}
public void remove(String objectId) {
log.info("remove(): {}", objectId);
try (Jedis jedis = jedisPool.getResource()) {
jedis.zrem(GEOSET, objectId);
}
}