ユーザーの地理座標を指定して、2人のユーザーが同じ場所にいるかどうかをリアルタイムで検出できるサービスを実装したいと考えています。
これをリアルタイムで実行してスケーリングするには、Redisのような分散インメモリデータストアを使用する必要があるようです。ジオハッシュを使用して調査しましたが、問題は、互いに近いポイントが常に同じハッシュプレフィックスを共有しない場合があることです。そして、私は2人のユーザーが隣り合って立っている場所に十分近いかどうかを調べたいので、ジオハッシュはやり過ぎかもしれません。
もちろん、単純な解決策は、地理座標のペアが互いに短い距離内にあるかどうかをテストすることです。しかし、AFAIK、Redis、およびその他のインメモリデータストアには、そのようなルックアップをサポートするための地理空間インデックスはありません。
これを実装するための最良の方法は何ですか?
この機能は Redis 3.2 + に組み込まれています。
しかし、古いバージョンの場合、問題はまだ存在しています。私はYin Qiwenの答えを取り入れてNodeのモジュールを作成しました。コードを調べることで、それがRedisをどのように使用するかを確認できます。彼の指示は完璧で、私はそれらをフォローして素晴らしい結果を得ることができました。 https://github.com/arjunmehta/node-georedis
同じアルゴリズムが本質的にネイティブコマンドに使用されるものです。
これは非常に高速であり、あらゆる種類の交差/ハバシンタイプの操作を回避します。 Yin Qiwenの方法の最もクールな点(私は思う)は、アルゴリズムの最も計算量の多い部分をクライアントに配布できることです(すべてがDBまたはサーバーで行われるのではなく)。
それは100%正確ではなく、事前構成された距離ステップを使用しますが、ほとんどのアプリケーションでは、私が想像するような正確な精度は必要ありません。
GISスタック交換 でYin Qiwenの記事も言い換えました。
すべてのリンケージでごめんなさい。 :P
一般的に、これはGeoHashとRedisのソート済みセットで実行できます。 Redisに空間インデックスサービスを実装する方法について説明する前に書いた設計があります。
このスレッドの他の回答で言及されているRedis地理版は、Redisに統合されています バージョン3.2以降 ( この以前のコメント も参照)。
新しいコマンドはここにあります(現時点ではベータ版):
これはあなたの質問に答えないことに気づきました...しかし、それは正しいツールだとは思いません。
PostgreSQL + PostGISは非常に優れたパフォーマンスを発揮します。メモリに収まる限りのデータベースを実行するようにPostgreSQLを設定できます。
PostGISは(私が思うに)rtreeインデックスを使用するので、興味のある種類の検索を実行するのは信じられないほど高速です。
WebSocketリクエストを発生させるバックエンドを使用すると、ほぼリアルタイムで実行できます。バックエンドが人物のGPS座標を受信するときはいつでも;空間ルックアップを実行します。 Webソケットを通じて該当するクライアントに通知します。
Tarantoolデータベースは、データをメモリに保持し、トランザクションログとしてディスクにプッシュし、RTreeタイプの空間インデックス(2次元だけでなく)と、そのようなインデックスに対する複数のNice操作(包含、オーバーラップ、距離)を備えています。
3Dスペースでオブジェクトを記述するレコードを保存および照会するための商用プロジェクトで使用しています。
http://tarantool.org/doc/book/box/box_index.html
https://github.com/tarantool/tarantool/wiki/R-tree-index-quick-start-and-usage
標準のクライアントとサンプルはLuaにありますが、データベース作成者が開発した他のクライアントがいくつかあります。 JavaクライアントをScalaアプリケーションで使用して成功しています。
データベースも非常に高速です-他のデータベースとの科学的な比較(空間データベースであるという側面は別として): http://airccse.org/journal/ijdms/papers/6314ijdms01.pdf
サンプル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);
}
}