Create & Find GeoLocation in mongoose
Asked Answered
V

3

23

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!

Vasos answered 25/8, 2015 at 8:55 Comment(0)
W
73

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",
            "spherical": 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.

Wyne answered 25/8, 2015 at 9:13 Comment(4)
thank you for your help! The storing of a user works now fine. I'll also have a look at your aggregate implementation, looks much nicer than the kind of hack I had beforeVasos
Thank you for this info, just please use "spherical" instead of "sperical" in your geo query.Homans
@ColoGhidini Can you please explain this: (Why is it better to aggregate) "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"Undergo
This method doesn't work anymore. Even after adding the index to the schema, it return the following error: $geoNear requires a 2d or 2dsphere index.Mcmullan
H
0

This worked for me as better solution for point and polygon.

var WeSchema = new Schema({
Location: {
    type: {
      type: String,
      enum: ['Point', 'Polygon']
    },
    coordinates: [Number] 
  }
});
WeSchema.index({Location: '2dsphere' });
Hermosa answered 8/1, 2021 at 4:19 Comment(0)
L
0

Am facing below error:

ApolloError: geoNear command failed: { ok: 0.0, errmsg: "no geo indices for geoNear" }

Code:

const mongoose = require("mongoose");

mongoose.Promise = global.Promise;

const ToDoSchema = new mongoose.Schema(
  {
    title: { type: String, required: true },
    gmap: { geoJson: { type: { type: String }, coordinates: [Number] } },
  },
  { timestamps: true }
);
ToDoSchema.index({ "gmap.geoJson": "2dsphere" });

export default mongoose.models.things_to_do ||
  mongoose.model("things_to_do", ToDoSchema, "things_to_do");
Legibility answered 4/8, 2023 at 11:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.