Markus Markus - 3 months ago 37
Node.js Question

Create & Find GeoLocation in mongoose

I'm trying to save the longitude/latitude of a location in my user Model and query it with

$geoNear
. I've looked at almost any page I could find on that topic, but still I'm not sure how to 1) create the model appropriately and how to query it.

here's the part of my model:

loc: {
//type: 'Point', // see 1
//type: String, // see 2
coordinates: [Number],
index: '2dsphere' // see 3
},


@1: According the documentation the type must be 'Point' if I want to store a point, but it throws

TypeError: Undefined type `Point` at `loc`
Did you try nesting Schemas? You can only nest using refs or arrays.


which makes sense, since I guess it should be a
String


@2: this doesn't immediately throw an error, but when I try to store a user I get:

name: 'ValidationError',
errors:
{ loc:
{ [CastError: Cast to String failed for value "[object Object]" at path "loc"]


@3: if I want to create the index like that I get the error:

TypeError: Undefined type `2dsphere` at `loc.index`
Did you try nesting Schemas? You can only nest using refs or arrays.


so I tried to store the index to what felt a little bit more appropriate to me like this:

userSchema.index({'loc': '2dsphere'});


which throws the following error when I try to save a new user:

- { [MongoError: Can't extract geo keys from object, malformed geometry?:{ coordinates: [ -73.97, 40.77 ] }]
name: 'MongoError',
message: 'Can\'t extract geo keys from object, malformed geometry?:{ coordinates: [ -73.97, 40.77 ] }',
ok: 1,
n: 0,
code: 16572,
errmsg: 'Can\'t extract geo keys from object, malformed geometry?:{ coordinates: [ -73.97, 40.77 ] }',
writeConcernError:
{ code: 16572,
errmsg: 'Can\'t extract geo keys from object, malformed geometry?:{ coordinates: [ -73.97, 40.77 ] }' } }
MongoError: Can't extract geo keys from object, malformed geometry?:{ coordinates: [ -73.97, 40.77 ] }





When I updated my model on the database to to a
$geoNear
command I updated my user like this:

loc: {coordinates: [-73.97, 40.77]}


and could successfully query it like this:

mongoose.model('users').db.db.command({
"geoNear": users,
"near": [
<latitude>,
<longitude>
],
"spherical": true,
"distanceMultiplier": 6378.1, // multiplier for kilometres
"maxDistance": 100 / 6378.1, // every user within 100 km
"query": { }


(I used db.command since from what I've read
$geoNear
is not available when using
find()
)

Everything I tried so far got me nowhere, so my question now is:


  • how should my mongoose model look like?

  • how can I store a user with his or her location in my database



Any help would be much appreciated!

Answer

If you want a schema to support GeoJSON, the you first need to construct that properly:

var userSchema = new Schema({
    loc: {
        type: { type: String },
        coordinates: [Number],
    }
});

That makes sure there is no confusion with the "type" keyword of the schema defintition. If you really want to support the whole range of GeoJSON types, then you can make that a bit looser:

var userSchema = new Schema({
    loc: {
        type: { type: String },
        coordinates: []
    }
});

Next you want to tie an index to the shema:

userSchema.index({ "loc": "2dsphere" });

Then of course define a model and store things correctly:

var User = mongoose.model( "User", userSchema );

 var user = new User({ 
     "loc": { 
         "type": "Point",
         "coordinates": [-73.97, 40.77]
     }
 });

Noting that your data must be in longitude then latitude order as supported by GeoJSON and all MongoDB geospatial query forms.

Next, rather than dig into obscure usages of database commands directly on the raw driver method, use things instead that are directly supported, and better. Such as $geoNear for the .aggregate() method:

User.aggregate(
    [
        { "$geoNear": {
            "near": {
                "type": "Point",
                "coordinates": [<long>,<lat>]
            },
            "distanceField": "distance",
            "sperical": true,
            "maxDistance": 10000
        }}
    ],
    function(err,results) {

    }
)

And now because the data is GeoJSON, the distances are already converted to meters so there is no need to do other conversion work.

Also note that is you have been messing around with this, unless you drop the collection any index you tried will still be there and that will likely cause problems.

You can drop all indexes from the collection in the mongodb shell with ease:

db.users.dropIndexes();

Or since you likely need to do some data re-shaping, then drop the collection and start again:

db.users.drop();

Set things up properly and you will have no problems.

Comments