23

我想实现一个服务,给定用户的地理坐标,可以实时检测两个用户是否在同一个位置。

为了实时执行此操作并进行扩展,我似乎应该使用像 Redis 这样的分布式内存数据存储。我已经使用 geohashing 进行了研究,但问题是彼此靠近的点可能并不总是共享相同的哈希前缀。并且 geohashing 可能是矫枉过正,因为我有兴趣找出两个用户是否足够靠近他们站在彼此旁边的位置。

当然,简单的解决方案只是测试地理坐标对是否彼此相距很小。但是 AFAIK、Redis 和其他内存数据存储没有地理空间索引来支持这种查找。

执行此操作的最佳方法是什么?

4

7 回答 7

17

此功能已融入Redis 3.2+

但是对于旧版本,问题仍然存在。我采纳了尹启文的回答,并为Node创建了一个模块,您可以通过查看代码来了解它是如何使用Redis的。他的指示是完美的,我能够遵循它们以获得很好的结果。 https://github.com/arjunmehta/node-georedis

相同的算法本质上是用于本机命令的算法。

它非常快,并且避免了任何类型的交集/haversine 类型的操作。尹启文的方法最酷的地方(我认为)是算法中计算量最大的部分可以分发给客户端(而不是全部发生在数据库或服务器上)。

它不是 100% 精确并使用预配置的距离步长,但对于大多数应用程序,您不需要我想象的精确精度。

我还转述了尹启文在GIS stack exchange上的文章。

对不起所有的联系。:P

于 2014-04-10T01:57:00.270 回答
15

一般来说,这可以通过 GeoHash 和 Redis 的 sorted set 来完成。之前写过一个设计,讲如何在redis上实现空间索引服务。

https://github.com/yinqiwen/ardb/wiki/Spatial-Index

于 2014-02-27T13:51:39.307 回答
7

也许你可以试试这个:

Redis地理版

你真的很想试试,效果很好。:)

于 2014-07-22T00:03:03.553 回答
5

我意识到这并不能回答您的问题……但我认为这不是正确的工具。

PostgreSQL + PostGIS 可以执行得非常非常好。您可以将 PostgreSQL 配置为尽可能多地运行数据库,因为它可以容纳在内存中。

PostGIS 使用(我认为)rtree 索引,因此执行您感兴趣的查找类型非常快。

使用触发 websocket 请求的后端将允许您执行几乎实时的操作。每当您的后端收到一个人的 GPS 坐标时;执行空间查找;并通过 websockets 通知适用的客户。

于 2013-12-06T02:09:31.940 回答
5

该线程中其他答案提到的 Redis 地理版自 3.2 版以来已集成到 Redis 中(另请参阅此较早的评论)。

您可以在此处找到新命令(目前处于测试阶段):

于 2015-07-11T16:38:12.887 回答
1

Tarantool 数据库将数据保存在内存中,将它们作为事务日志推送到磁盘,具有 RTree 类型的空间索引(不仅是二维的)和对此类索引的许多不错的操作(包含、重叠、距离)。

我在一个商业项目中使用它来存储和查询描述 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

于 2016-01-20T19:33:40.900 回答
0

我想分享一个 Redis Geography 版的示例 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);
    }
}
于 2015-08-20T06:00:58.653 回答