我希望实现一个服务,能够根据用户的地理坐标实时检测两个用户是否在同一位置。
为了实现实时性和可扩展性,我似乎应该选择一个分布式内存数据存储系统,如Redis。我已经研究过使用地理哈希技术,但问题是接近的点不一定总是共享相同的哈希前缀。而且,由于我只想找出两个用户是否足够接近,站在彼此旁边,所以地理哈希可能过于复杂。
当然,简单的解决方案就是测试一对地理坐标是否在彼此附近。但据我所知,Redis和其他内存数据存储系统并没有提供空间索引来支持那种查询。
那么,最佳实现方法是什么?
我希望实现一个服务,能够根据用户的地理坐标实时检测两个用户是否在同一位置。
为了实现实时性和可扩展性,我似乎应该选择一个分布式内存数据存储系统,如Redis。我已经研究过使用地理哈希技术,但问题是接近的点不一定总是共享相同的哈希前缀。而且,由于我只想找出两个用户是否足够接近,站在彼此旁边,所以地理哈希可能过于复杂。
当然,简单的解决方案就是测试一对地理坐标是否在彼此附近。但据我所知,Redis和其他内存数据存储系统并没有提供空间索引来支持那种查询。
那么,最佳实现方法是什么?
这个功能已经内置于Redis 3.2+.
但对于旧版本仍存在问题。我采用了Yin Qiwen的答案,并为Node创建了一个模块,您可以通过检查代码来了解它如何使用Redis。他的说明非常完美,我能够遵循它们获得很好的结果。 https://github.com/arjunmehta/node-georedis
本质上,相同的算法用于原生命令。
它非常快速,并避免任何种类的交集/ haversine类型操作。 Yin Qiwen方法中最酷的事情(我认为)是算法中最具计算强度的部分可以分布到客户端(而不是全部在DB或服务器上发生)。
它不是100%精确的,并使用预配置的距离步骤,但对于大多数应用程序,您可能不需要精确的精度。
我还用自己的话重新表达了Yin Qiwen在GIS堆栈交换中的文章。
抱歉我放了这么多链接。 :P
通常情况下,可以通过GeoHash和Redis的有序集合来实现。我之前写过一个设计文档,介绍如何在Redis上实现空间索引服务。
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
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);
}
}