web-dev-qa-db-fra.com

Comment utiliser Redis et la recherche de proximité géographique pour trouver deux utilisateurs au même endroit?

Je souhaite implémenter un service qui, en fonction des coordonnées géographiques des utilisateurs, peut détecter si deux utilisateurs se trouvent au même endroit en temps réel.

Pour pouvoir le faire en temps réel et à l’échelle, il me semble que je devrais utiliser un datastore distribué en mémoire tel que Redis. J'ai effectué des recherches sur l'utilisation de geohashing, mais le problème est que les points proches les uns des autres ne partagent pas toujours le même préfixe de hachage. Et geohashing peut être exagéré puisque je voudrais savoir si deux utilisateurs sont assez proches l'un de l'autre, l'un à côté de l'autre.

La solution simple consiste bien entendu à vérifier si les paires de coordonnées géographiques se situent à une faible distance les unes des autres. Toutefois, autant que je sache, Redis et d’autres bases de données en mémoire ne disposent pas d’indexation géospatiale pour prendre en charge ce type de recherche. 

Quelle est la meilleure façon de mettre en œuvre cela?

22
Simian

Cette fonctionnalité est intégrée à Redis 3.2+ .

Mais pour les anciennes versions, le problème persiste. J'ai pris la réponse de Yin Qiwen et créé un module pour Node. Vous pouvez voir comment il utilise Redis en examinant le code. Ses instructions sont parfaites et j'ai pu les suivre pour obtenir d'excellents résultats . https://github.com/arjunmehta/node-georedis

Le même algorithme est essentiellement ce qui est utilisé pour les commandes natives.

Il est très rapide et évite tout type d'opération de type intersection/haversine. La chose la plus intéressante (je pense) à propos de la méthode de Yin Qiwen est que les parties les plus intenses en calcul de l’algorithme peuvent être distribuées aux clients (au lieu de tout se passer dans la base de données ou sur le serveur).

Ce n'est pas précis à 100% et utilise des pas de distance préconfigurés, mais pour la plupart des applications, vous n'aurez pas besoin de précision exacte, je l'imagine.

J'ai également paraphrasé l'article de Yin Qiwen lors de l'échange de piles GIS .

Désolé pour tout le lien. : P

16
Arjun Mehta

Généralement, cela pourrait être fait par les ensembles triés de GeoHash et Redis. Avant de vous expliquer comment implémenter un service d'index spatial sur Redis, il existe un modèle que j'ai écrit. 

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

15
yinqiwen

Peut-être que vous pouvez essayer celui-ci:

Redis Geography Edition

Vous voulez vraiment l'essayer, ça marche génial . :)

7
mzalazar

L'édition géographique Redis mentionnée par d'autres réponses dans ce fil de discussion a été intégrée à Redis depuis la version 3.2 (voir également ce commentaire précédent ).

Vous pouvez trouver les nouvelles commandes ici (en version bêta pour le moment):

5
nha

Je réalise que cela ne répond pas à votre question ... mais je ne pense pas que ce soit le bon outil.

PostgreSQL + PostGIS peut vraiment, très bien fonctionner. Vous pouvez configurer PostgreSQL pour qu'il exécute à peu près autant de la base de données qu'il peut en contenir en mémoire.

PostGIS utilise (je pense) des index d'arbre, il est donc extrêmement rapide d'effectuer le type de recherche qui vous intéresse. 

L'utilisation d'un backend qui déclenche des requêtes websocket vous permettrait d'effectuer à peu près en temps réel. Chaque fois que votre serveur reçoit les coordonnées GPS d'une personne; effectuer la recherche spatiale; et notifiez les clients concernés via des websockets.

5
jreid42

La base de données Tarantool conserve les données en mémoire, les place sur le disque en tant que journaux de transactions, possède un index spatial de type RTree (pas uniquement bidimensionnel) et plusieurs opérations de Nice sur cet index (confinement, superposition, distance).

Je l'utilise dans un projet commercial pour stocker et interroger des enregistrements décrivant des objets dans un espace 3D.

http://tarantool.org/doc/book/box/box_index.html

https://github.com/tarantool/tarantool/wiki/R-tree-index-quick-start-and-usage

Le client standard et les exemples sont en Lua, mais il existe quelques autres clients développés par les auteurs de la base de données. J'utilise le client Java dans une application Scala avec succès.

La base de données est également très rapide - voici une comparaison scientifique avec d’autres bases de données (en mettant de côté un aspect de base de données spatiale): http://airccse.org/journal/ijdms/papers/6314ijdms01.pdf

1
Wojciech Kaczmarek

J'aimerais partager un exemple de code Java pour l'édition Redis Geography.

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);
    }
}
0