简体   繁体   中英

How to find data of nearest location from mongodb collection using mongoose?

I am looking for list of nearest branch of branchId [Array], eg :[109,110,115]

// My Schema
var BranchSchema = new Schema({
branchId : { type : Schema.Types.ObjectId },
branchName : { type : String },
branchAddress : {
    Address:{ type : String, required : false},
    City :{ type : String, required : false },
    Country : { type : String , required : false}
},
loc: {
    type: [Number],  // [<longitude>, <latitude>]
    index: '2d'      // create the geospatial index
}

});

    clubList.find({
            $and:[{ 
                    loc: { $near: coords, $maxDistance: maxDistance }
                },
                {_id :{ $in: branchId}} //branchId :  [108,109,110]
            ]},function(err, branchData){
            if (err) 
            {
               console.log(err);
            }
           else
           {
               console.log(branchData); 
            // Expecting sort near by branch of array [108,109,110]
           }

Data should be sorted in near to far order.

It looks like you are actually asking for an $or condition instead of $and . That would make sense considering if you actually wanted "both" the results from:

  • The $near query results
  • "as well as" the documents that match the $in condition given to the _id values.

As an $and condition the query can be written as:

clublist.find({
    "loc": { "$near": coords, "$maxDistance": maxDistance },
    "_id": { "$in": branchId }    // branchId is the array [108,109,110]
},function(err,branchData) {
  /* Data needs to be "both" near as well has having the supplied 
     _id values. So at maximum only "three" results, provided all
     "three" are within the maxDistance
   */
})

So with that considered it seems most likely you mean $or , but then there is another problem.

You cannot use $or with a query operation like $near that requires a geospatial index. So an operation like this will error:

clublist.find({
    "$or": [
        { "loc": { "$near": coords, "$maxDistance": maxDistance } },
        { "_id": { "$in": branchId } }    // branchId is the array [108,109,110]
    ]
},function(err,branchData) {
   // err:  "geoNear must be top-level expr"
})

In order to do this, you actually want seperate queries to run and then combine the results. Since these are actually seperate results then you likely also want the "distance" to be included in order to "sort" on the combination.

A good tool for this is async.parallel , since it allows you fetch results from different operations and work in the one callback. Also there is aggregation $geoNear which actually "projects" the "distance" into the results, which is a vital part of sorting the overall result:

async.parallel(
  [
    function(callback) {
      clublist.aggregate(
        [
          { "$geoNear": {
            "near": coords,
            "distanceField": "distance",
            "maxDistance": maxDistance
          }},
          { "$limit": 50 }
        ],
        callback
      )
    },
    function(callback) {
      clublist.aggregate(
        [
          { "$geoNear": {
            "near": coords,
            "distanceField": "distance",
            "query": { "_id": { "$in": branchId } }
          }}
        ],
        callback
      )
    }
  ],
  function(err,results) {
    if (err) throw err;
    var branchData = [];
    branchData = results.forEach(function(el) { 
       branchData = branchData.concat(el);
    });

    // Sort and return the 50 nearest
    branchData = branchData.sort(function(a,b) {
      return a.distance > b.distance
    }).slice(0,50)
  }
)

In this case you are running each query seperately, where one contrains the result by "maxDistance" and the other is contrained by the arguments to $in . Since the "combined" result would be greater than the set "limit" you need to sort that result by distance and just return the total limit from the combination.

That is how you would do it where you wanted the _id selection to be considered but the results might actually return a "distance" that would otherwise not include then within the $near .

If however your intent was always to have the _id selection "on top" of the results, you could then just use a regular query and "inject" a distance of 0 for those results:

async.parallel(
  [
    function(callback) {
      clublist.aggregate(
        [
          { "$geoNear": {
            "near": coords,
            "distanceField": "distance",
            "maxDistance": maxDistance
          }},
          { "$limit": 50 }
        ],
        callback
      )
    },
    function(callback) {
      clublist.find({ "_id": { "$in": branchId }}).toArray(function(err,branchData) {
        branchData = branchData.map(function(doc) {
          doc.distance = 0;
        });
        callback(err,branchData);
      })
    }
  ],
  function(err,results) {
    if (err) throw err;
    var branchData = [];
    branchData = results.forEach(function(el) { 
       branchData = branchData.concat(el);
    });

    // Sort and return the 50 nearest
    branchData = branchData.sort(function(a,b) {
      return a.distance > b.distance
    }).slice(0,50)
  }
)

Then the same sorting keeps those values on top as well as returning any results from the other query operation.

There is of course the "chance" that the specified _id values are returned in "both" queries. But if that is the case or even likely, then you can simply match the array content for any "duplicate" _id values before you actually perform the .slice() operation or generally before returning final results. That is a simple process using an object with unique keys and then going back to an array.

Something like:

function(err,results) {
    if (err) throw err;
    var branchData = [];
    branchData = results.forEach(function(el) { 
       branchData = branchData.concat(el);
    });

    var uniqueBranch = {};

    // Just keep unique _id for smallest distance value
    for ( var idx in branchData ) {
      if ( ( uniqueBranch.hasOwnProperty(branchData[idx]._id) ) 
           && ( branchData[idxx].distance > uniqueBranch[branchData[idx]._id].distance ) )
         continue;
      uniqueBranch[branchData[idx]._id] = branchData[idx];
    }

    // Go back to array, sort and slice
    branchData = Object.keys(uniqueBranch).map(function(k) {
        return uniqueBranch[k];
    }).sort(function(a,b) {
        return a.distance > b.distance;
    }).slice(0,50);

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM