MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Filtrer les documents par distance stockée dans le document avec $near

En supposant que vous avez déjà travaillé sur les données d'événement au fur et à mesure que vous les recevez et que vous les avez en main (si ce n'est pas le cas, alors c'est une autre question, mais regardez curseurs tailable ), alors vous devriez avoir un objet avec ces données pour lesquelles interroger les utilisateurs.

Ce n'est donc pas un cas pour l'évaluation de JavaScript avec $where , car il ne peut pas accéder aux données de requête renvoyées par un $near opération quand même. Ce que vous voulez à la place est $geoNear du cadre d'agrégation. Cela peut projeter la "distance" trouvée à partir de la requête et permettre à une étape ultérieure de "filtrer" les résultats par rapport à la valeur stockée par l'utilisateur pour la distance maximale qu'il souhaite parcourir pour se rendre aux événements publiés :

// Represent retrieved event data
var eventData = {
  eventLocation: {
    latlong: [long,lat]
  }
};

// Find users near that event within their stored distance
User.aggregate(
  [
    { "$geoNear": {
      "near": {
        "type": "Point",
        "coordinates": eventData.eventLocation.latlong
      },
      "distanceField": "eventDistance",
      "limit": 100000,
      "spherical": true
    }},
    { "$redact": {
      "$cond": {
        "if": { "$lt": [ "$eventDistance", "$maxDistance" ] },
        "then": "$$KEEP",
        "else": "$$PRUNE"
      }
    }}
  ]
  function(err,results) {
    // Work with results in here
  }
)

Maintenant, vous devez faire attention au nombre renvoyé, car puisque vous semblez stocker dans des "paires de coordonnées héritées" au lieu de GeoJSON, la distance renvoyée par cette opération sera en radians et non en distance standard. Donc, en supposant que vous stockez en "miles" ou "kilomètres" sur les objets utilisateur, vous devez calculer via la formule mentionnée dans le manuel sous "Calculer les distances à l'aide de la géométrie sphérique" comme mentionné dans le manuel.

Les bases sont que vous devez diviser par le rayon équatorial de la terre, soit 3 963,2 miles ou 6 378,1 kilomètres à convertir pour une comparaison avec ce que vous avez stocké.

L'alternative consiste à stocker dans GeoJSON à la place, où il y a une mesure cohérente en mètres.

En supposant "kilomètres" que la ligne "if" devient :

"if": { "$lt": [
    "$eventDistance",
    { "$divide": [ "$maxDistance", 6,378.1 ] }
 ]},

Pour comparer de manière fiable votre valeur de kilomètre stockée au résultat en radian récupéré.

L'autre chose à savoir est que $geoNear a une "limite" par défaut de 100 résultats, vous devez donc "gonfler" l'argument "limite" au nombre d'utilisateurs attendus. Vous voudrez peut-être même le faire dans des "listes de plages" d'ID utilisateur pour un très grand système, mais vous pouvez aller aussi loin que la mémoire le permet dans une seule opération d'agrégation et éventuellement ajouter allowDiskUse si nécessaire.

Si vous ne réglez pas ce paramètre, seuls les 100 résultats les plus proches ( default ) seront renvoyés, ce qui pourrait même ne pas convenir à votre prochaine opération de filtrage de ceux " proches " de l'événement pour commencer. Faites preuve de bon sens, car vous avez sûrement une distance maximale pour filtrer même les utilisateurs potentiels, et cela peut également être ajouté à la requête.

Comme indiqué, le point ici est de renvoyer la distance pour comparaison, donc la prochaine étape est le $redact opération qui peut filtrer la propre valeur de "distance de déplacement" de l'utilisateur par rapport à la distance renvoyée depuis l'événement. Le résultat final donne uniquement les utilisateurs qui se situent dans leur propre contrainte de distance par rapport à l'événement qui seront éligibles pour la notification.

C'est la logique. Vous projetez la distance entre l'utilisateur et l'événement, puis vous comparez à la valeur stockée par l'utilisateur la distance qu'il est prêt à parcourir. Pas de JavaScript, et tous les opérateurs natifs qui le rendent assez rapide.

De plus, comme indiqué dans les options et le commentaire général, je vous suggère vraiment d'utiliser un index "2dsphere" pour un calcul précis de la distance sphérique ainsi que la conversion en stockage GeoJSON pour votre stockage de coordonnées dans vos objets de base de données, car ce sont tous deux des normes générales qui produire des résultats cohérents.