Sorting collection by distance


#1

Hi guys,

Im trying to sort my Mongo collection based on distance from a set of coordinates. I think I am pretty close to a solution right now, but I get some errors that i’m not sure how to solve:

I20151018-21:48:33.969(2)? Exception from sub shops id 8fQiTNRyw5be683Pg Error: Exception while polling query {"collectionName":"shops","selector":{"location":{"$near":{"$geometry":{"type":"Point","coordinates":[56.157981,10.209222]}},"$maxDistance":2000}},"options":{"transform":null}}: Can't canonicalize query: BadValue geo near accepts just one argument when querying for a GeoJSON point. Extra field found: $maxDistance: 2000
I20151018-21:48:33.969(2)?     at Object.Future.wait (/Users/caspersorensen/.meteor/packages/meteor-tool/.1.1.9.r5iaer++os.osx.x86_64+web.browser+web.cordova/mt-os.osx.x86_64/dev_bundle/server-lib/node_modules/fibers/future.js:398:15)
I20151018-21:48:33.969(2)?     at [object Object]._.extend.addHandleAndSendInitialAdds (packages/mongo/observe_multiplex.js:55:1)
I20151018-21:48:33.969(2)?     at [object Object].MongoConnection._observeChanges (packages/mongo/mongo_driver.js:1236:1)
I20151018-21:48:33.969(2)?     at [object Object].Cursor.observeChanges (packages/mongo/mongo_driver.js:911:1)
I20151018-21:48:33.969(2)?     at Function.Mongo.Collection._publishCursor (packages/mongo/collection.js:308:1)
I20151018-21:48:33.970(2)?     at [object Object].Cursor._publishCursor (packages/mongo/mongo_driver.js:892:1)
I20151018-21:48:33.970(2)?     at [object Object]._.extend._runHandler (livedata_server.js:1059:13)
I20151018-21:48:33.970(2)?     at [object Object]._.extend._startSubscription (livedata_server.js:842:9)
I20151018-21:48:33.970(2)?     at [object Object]._.extend.protocol_handlers.sub (livedata_server.js:614:12)
I20151018-21:48:33.970(2)?     at livedata_server.js:548:43
I20151018-21:48:33.970(2)?     - - - - -
I20151018-21:48:33.970(2)?     at [object Object]._.extend._pollMongo (packages/mongo/polling_observe_driver.js:156:1)
I20151018-21:48:33.970(2)?     at Object.task (packages/mongo/polling_observe_driver.js:85:1)
I20151018-21:48:33.970(2)?     at [object Object]._.extend._run (packages/meteor/fiber_helpers.js:147:1)
I20151018-21:48:33.970(2)?     at packages/meteor/fiber_helpers.js:125:1

My publish function:

Meteor.publish('shops', function() {
    return Shops.find({location: {$near:{$geometry:{ type: 'Point', coordinates: [56.1579809999999995, 10.2092220000000005]}}, $maxDistance: 2000}});
});

My helper function:

Template.shopsList.helpers({
   shops: function() {
        return Shops.find({location: {$near:{$geometry:{ type:'Point', coordinates:[56.1579809999999995, 10.2092220000000005]}},
            $maxDistance: 2000}});
    }
});

At first I tried to run the query only inside my helper function, but it seemed to return the same result no matter which options i gave it… Any help is appreciated here, thanks!


#2

minimongo does not support geo.

and that $geometry in method should not be at all if I remember correctly, all you need is directly point there and maxdistance. Should work if you have 2dsphere index on that document parameter.

I suggest trying it first directly in mongo shell, than moving to meteor world.


#3

Doing the following does not yield any results:

Server:

Shops = new Meteor.Collection('shops');

Shops._ensureIndex({loc : '2dsphere' });

Meteor.publish('shops', function() {
    return Shops.find({loc: {$near: [56.1579809999999995, 10.2092220000000005] }});
});

Client:

Template.shopsList.helpers({
    shops: function() {
        return Shops.find();
    }
});

Using this query in the mongo shell does not yield me any results either though, so maybe my collection is wrongly configured?

db.shops.find({loc: {$near:{ type:'Point', coordinates: [56.15798099999, 10.20922200000] }}});

Thanks for your help!


#4

BTW, this is schema I am using, but not yet doing geo search

SimpleSchema.messages
  lonOutOfRange: '[label] longitude should be between -90 and 90'
  latOutOfRange: '[label] latitude should be between -180 and 180'

LocationSchema = new SimpleSchema
  type:
    type: String,
    allowedValues: ['Point']
    defaultValue: "Point"
  coordinates:
    type: [Number]
    decimal: true
    minCount: 2
    maxCount: 2
    custom: ->
      return "lonOutOfRange" unless -90 <= @value[0] <= 90
      return "latOutOfRange" unless -180 < @value[1] <= 180

And then using it in collection

 "location":
    type: LocationSchema
    index: '2dsphere'
    optional: true

And I think u were close, just need to add that $maxDistance and forget about $geometry :smiley:
But I can be wrong, just brainstorming.


#5

I tried doing this:

Shops.find({loc: {$near: [56.1579809999999995, 10.2092220000000005], $maxDistance: 2000}});

Maybe my data is wrong?

{
    "_id" : "w7XD332vKic4ZWC53",
    "shop_title" : "Baresso",
    "location" : {
        "type" : "Point",
        "coordinates" : [ 
            56.1635940000000033, 
            10.2106739999999991
        ]
    }
}

#6

Why do you try to find loc if you have field location?!

return Shops.find({
  'location': {
    $near: {
      $geometry: {
         type: "Point",
         coordinates: [lng, lat]
      },
      $maxDistance: radius
    }
   }
 });

And Shops._ensureIndex({'location' : '2dsphere' });
Your data is correct (-:


#7

It works! I pasted your code in, and changed the values, so I guess I was missing the $geometry point?

Anyway, thank you so so much! :smile:


#8

Most importantly - what works
The rest is not important (((-:


#9

Haha, I guess that has some truth to it ;)…

Another topic:
Is there anything special I have to do in order to obtain the user’s position serverside?

var currentLocation = Geolocation.currentLocation();

console.log(currentLocation);

I get this error:

I20151019-09:35:37.612(2)? Exception from sub shops id vyHS57tD4Kw2ZZGCM ReferenceError: Geolocation is not defined
I20151019-09:35:37.612(2)?     at [object Object]._handler (server/methods.js:5:27)
I20151019-09:35:37.612(2)?     at maybeAuditArgumentChecks (livedata_server.js:1692:12)
I20151019-09:35:37.612(2)?     at [object Object]._.extend._runHandler (livedata_server.js:1023:17)
I20151019-09:35:37.612(2)?     at [object Object]._.extend._startSubscription (livedata_server.js:842:9)
I20151019-09:35:37.612(2)?     at [object Object]._.extend.protocol_handlers.sub (livedata_server.js:614:12)
I20151019-09:35:37.613(2)?     at livedata_server.js:548:43

Getting the location works clientside, but not serverside maybe ? Or do I have to send the values to the server from my clientside instead?


#10

sry for pointing you out of correct direction, I should not have internet access on phone :smiley:


#11

Haha, no harm done, I guess we both learn a bit then :wink:


#12

Yes, Geolocation is only on client side: Provides reactive geolocation on desktop and mobile.

You have to send coordinates from client to server through subscription or like method’s arguments


#13

I thought that Publish/subscribe was a one way channel, that you publish to the client, and the client then subscribes to that data end of story. How would I send data back from my subscription?


#14

For a “typical” collection (one which exists on the client and server), a subscription can be thought of as a window into a subset of the whole collection. In principle, that means if you modify a document in your local collection (or add a new one, delete one, etc), that modification will be persisted to the the actual (physical) collection.

It is, thankfully, not as simple as that (unless you have the insecure package added), since you will need to include appropriate server-side allow/deny rules.


#15

Hey robfallows,

Thanks for clearing that up for me :). So I can also share values through that “window” which does not need persistence? Fx. the users currentLocation?


#16

Hmm. That depends …

If you modify/add/delete a document using one of the standard minimongo methods (update, insert, remove) that modification has the potential to change the physical database - and will if your allow/deny rules permit it.

On the other hand, you may change your documents as you please on the client, and if you don’t go through minimongo, they’ll have no impact elsewhere (this is essentially what collection transforms are doing).

On the other other hand (:wink:), if you want to disseminate non-persistent data to connected users, then you need to look at alternative techniques (for example client-only collections with roll-your-own publications coupled with Meteor.call/Meteor.methods).


#17

you can expect additional arguments in publish function and set them in subscribe.
For example user location, that way you can subscribe only to documents based on user input.
And if you do it in autorun on client and use reactive data source to pass values to these arguments, it will automatically re-subscribe when your position change.

For example this article, if you in reactive part instead of changing map position subscribe to your publication http://meteorcapture.com/reactive-geolocation-with-google-maps/


#18

@shock Ill have a stab at it later tonight, will let you know if I can make it work.

I actually used that tutorial as my starting point, it’s really good!