MongoDB: $geoNear query and $match aggregation return empty result

I need to get documents within a specific radius which match a simple query.

The query works well in Robomongo:

db.getCollection('Options').aggregate([
    {"$geoNear":
        {"near": {"type":"Point","coordinates":[-0.1251485,51.5174914]},
        "spherical":true,"distanceField":"dist","maxDistance":2000,
        "query": {
            "field": "xxx"
        }}
    },
    {"$sort":{"dist":-1}}])

but returns an empty result in Meteor. I have tried different fields and queries.

Options.aggregate([
        {"$geoNear": {
            "near": {"type":"Point","coordinates":[-0.1251485,51.5174914]},
            "spherical":true,"distanceField":"dist","maxDistance":2000,
            "query": {
                "field": "xxx"
            }
        }},
        {"$sort":{"dist":-1}}
    ], (err, res) => {
        console.log(err);
        console.log(JSON.stringify(res));
    });

I have also tried to use $match in the aggregation pipeline instead - works in Robomongo, empty result in Meteor.

Options.aggregate([
        {$geoNear: {
            "near": {"type":"Point","coordinates":[-0.1251485,51.5174914]},
            "spherical":true,"distanceField":"dist","maxDistance":2000
        }},
        {$match: {"field": "xxx"}},
        {$sort:{"dist":-1}}
    ], (err, res) => {
        console.log(err);
        console.log(JSON.stringify(res));
    });

In fact, any $match aggregation seems to be empty.

Thanks a lot for your help & ideas!

  • Same results with Options.rawCollection().aggreate(…) or when checking the results only instead of writing a callback
1 Like

Are you using an aggregation package (like meteorhacks:aggregate)? There is no aggregate method on a minimongo collection object.

If not, you will have to use rawCollection().aggregate - but note that this returns a Promise if a callback is not used. You could use, for example:

const result = Promise.await(Options.rawCollection().aggregate([
  {
    $geoNear: {
      near: { type: 'Point', coordinates: [-0.1251485, 51.5174914] },
      spherical: true, distanceField: 'dist', maxDistance: 2000,
    },
  },
  { $match: { field: 'xxx' } },
  { $sort: { dist: -1 } },
]));

Thanks for the quick reply!

Yes, I use meteorhacks:aggregate, I have also tried rawCollection().aggregate, with callbacks and promises - unfortunately, same result.

This may be a stupid question - but it should work - so … Does your Options object definitely correspond to the same collection you’re accessing with Robomongo?

Haha a good one - it is exactly the same collection. find() queries run perfectly

I’ve never used meteorhacks:aggregate, but it’s not been worked on for a long time and that would make me nervous.

The underlying rawCollection().aggregate() method is definitely the way I would go (and have used successfully).

However, the Promise returns a cursor, not an array of results: possibly use toArray() to get values (btw, I don’t think $match and $sort are needed). Maybe something like:

const cursor = Promise.await(Options.rawCollection().aggregate([
  {
    $geoNear: {
      near: { type: 'Point', coordinates: [-0.1251485, 51.5174914] },
      spherical: true, distanceField: 'dist', maxDistance: 2000,
    },
  },
]));
const result = Promise.await(cursor.toArray());
console.log(result);

meteorhacks:aggregate actually does nothing but wrapping rawCollection() in a prototype function using Meteor.wrapAsync(), so it’s not too dangerous to use it.

The query itself has worked & works without $match or the query parameter for $geoNear. However, I need to filter for the field. We can disregard the $sort for now.

i followed you sample here with my own code:

import { Promise } from "meteor/promise";

Meteor.publish("timeSheet", function() {
  // Meteor._sleepForMs(10000);
  const cursor = Promise.await(
    TimeSheet.rawCollection().aggregate([
      {
        $match: {
          timeIn: {
            $exists: true
          }
        }
      },
      {
        $project: {
          year: { $year: "$timeIn" },
          month: { $month: "$timeIn" },
          _id: "$empId",
          timeIn: 1,
          timeOut: 1,
          createdAt: 1
        }
      }
    ])
  );

  const result = Promise.await(cursor.toArray());
  console.log(result);
  return result;
});

but i still get this error…

Do you have any advise?

got my solution below…

import { Promise } from "meteor/promise";
import { Random } from "meteor/random";

Meteor.publish("timeSheet", function() {
  // Meteor._sleepForMs(10000);
  const self = this;
  const cursor = Promise.await(
    TimeSheet.rawCollection().aggregate([
      {
        $match: {
          timeIn: {
            $exists: true
          }
        }
      },
      {
        $project: {
          _id: "$empId",
          year: { $year: "$timeIn" },
          month: { $month: "$timeIn" },
          timeIn: 1,
          timeOut: 1,
          createdAt: 1
        }
      }
    ])
  );

  const result = Promise.await(cursor.toArray());

  _.each(result, function(cursor) {
    console.log(cursor);
    self.added("TimeSheet", Random.id(), cursor);
  });

  self.ready();
});

are you sure this is a working solution?

I tried to the same pub/sub as yours. I can print the cursor foreach result and I can see the dist field, but the client doesn’t get subscribed. Here’s my code below

Meteor.publish("Common.UserFind",function(){
                var self = this;
                var me = Meteor.users.find(this.userId);
                Meteor.users._ensureIndex({location:'2dsphere'});
		const cursor = Promise.await(Meteor.users.rawCollection().aggregate([{
		    $geoNear: {
		      near: { type: 'Point', coordinates:  me.location.coordinates },
		      spherical: true, distanceField: 'dist', 
		    },
		  },{$match : {_id : {$in : query}}}
		]));
		const result = Promise.await(cursor.toArray());

	  	_.each(result, function(cursor) {
		    console.log(cursor.dist);
		    self.added("Common.UserFind", Random.id(), cursor);
	  	});

	  	self.ready();
});

The client looks like follows

Template.UserContacts.rendered=function(){
          this.autorun(() => {
                  this.subscribe("Common.UserFind");
         })
})

What do you think about my code?

I ended up using meteor call and returning result as array