Ben
Ben

Reputation: 6086

Geospatial Box query - Mongoose, Node.js MongoDB - returns no results

I am attempting to run a Geo Box query using Mongoose however do not get any results. I have built out a simplified test case:

var mongoose = require('mongoose');

// Schema definition
var locationSchema = mongoose.Schema({
    userid: { type : [Number], required: true},
    loc: {
        'type': {
            type: String,
            required: true,
            enum: ['Point', 'LineString', 'Polygon'],
            default: 'Point'
        },
        coordinates: []
    },
    tags:   { type : [String], index: true, required: true },
    create_date : {type: Date, "default": Date.now()}
});

locationSchema.index({ 'loc.coordinates': '2dsphere' });
var Location = mongoose.model('Location', locationSchema);

mongoose.connect('mongodb://localhost/test');

var chkin = new Location({
    userid: 1,
    loc: { type: 'Point', coordinates: [-122.424088, 37.529876] },
    tags:"foo"
});

chkin.save(function (err, locations) {
    console.log('SAVE err: ' + err)
    console.log('SAVE locations: ' + locations)
    console.log('');
    var query = Location.find(
        { "coordinates" :
            { "$geoWithin" :
                // <bottom left coordinates>  - <upper right coordinates> - long,lat
                { "$box" :[[-122.610168,37.598167], [-122.288818,37.845833]] }
            }
        }, function (err, docs) {
            console.log('FIND err: '+ err)
            console.log('FIND locations: '+docs)
        });
});

Console output:

SAVE err: null
SAVE locations: { __v: 0,
  _id: 53cc7a3ea44a9bc70634fdc6,
  create_date: Sun Jul 20 2014 19:26:06 GMT-0700 (PDT),
  tags: [ 'foo' ],
  loc: { coordinates: [ -122.424088, 37.529876 ], type: 'Point' },
  userid: [ 1 ] }

FIND err: null
FIND locations: 

Can anyone advise where my error is?

Upvotes: 3

Views: 2916

Answers (1)

Neil Lunn
Neil Lunn

Reputation: 151112

There are several problems in what you are coding up here. Best to look at a whole working listing and break that down:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;


var locationSchema = new Schema({
  userid: { type: Number, required: true },
  loc: {
    type: {
      type: "String",
      required: true,
      enum: ['Point', 'LineString', 'Polygon'],
      default: 'Point'
    },
    coordinates: [Number]
  },
  tags: [{ type: String, index: true, required: true }],
  create_date: { type: Date, default: Date.now }
});

locationSchema.index({ 'loc': '2dsphere' });
var Location = mongoose.model( 'Location', locationSchema );

mongoose.connect( 'mongodb://localhost/geotest' );

async.series(
  [

    function(callback) {
      Location.remove(function(err) {
        if (err) throw err;
        callback();
      });
    },

    function(callback) {
      var loc = new Location({
        userid: 1,
        loc: {
          type: 'Point',
          coordinates: [ -122.4481201171875, 37.71370177998719 ]
        },
        tags: "foo"
      });

      loc.save(function(err,loc) {
        if (err) throw err;
        console.log( "Location: %s", JSON.stringify( loc, undefined, 4 ) );
        callback();
      });
    },

    function(callback) {
      var query = Location.find({
        "loc": {
          "$geoWithin": {
            "$geometry": {
              "type": "Polygon",
              "coordinates": [[
                [ -122.610168, 37.598167 ],
                [ -122.288818, 37.598167 ],
                [ -122.288818, 37.845833 ],
                [ -122.610168, 37.845833 ],
                [ -122.610168, 37.598167 ]
              ]]
            }
          }
        }
      });
      query.exec(function(err,docs) {
        if (err) throw err;
        console.log( "Docs: %s", JSON.stringify( docs, undefined, 4 ) );
        callback();
      });
    }

  ]
);

Not directly related but seemingly that you did not mean to do is how you defined some elements in your schema like this:

  userid: { type: [Number], required: true },

That defines the field as an "Array", and it would seem you really don't mean to in this case. If you did then it really makes more sense to write like this:

  userid: [{ type: Number, required: true }],

But for the sake of this listing it is just shown as a plain field. The "tags" field makes sense as an array and has been defined that way.

The next thing to look at is the index definition. This may have been a result of trying things that did not work, but generally if you are defining a GeoJSON structure under "loc" in your documents, then that is the field you need to index on. Which possibly is related to the next part.

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

When you are querying on GeoJSON formatted objects, helper operator such as $box cannot be used:

..."The $box operator returns documents based on grid coordinates and does not query for GeoJSON shapes."

So when you are actually using GeoJSON then you must specify as a "Polygon" or "MultiPolygon" type of bounds in order to match GeoJSON objects within those bounds. At any rate, the field supplied to $geoWithin has to be the field that holds the index. You where supplying "coordinates" which was not a valid top level field. Depending on the index field you are on "loc" or "loc.coordinates":

      var query = Location.find({
        "loc": {
          "$geoWithin": {
            "$geometry": {
              "type": "Polygon",
              "coordinates": [[
                [ -122.610168, 37.598167 ],
                [ -122.288818, 37.598167 ],
                [ -122.288818, 37.845833 ],
                [ -122.610168, 37.845833 ],
                [ -122.610168, 37.598167 ]
              ]]
            }
          }
        }
      });

Finally, the "Point" you were looking for lies outside of the "box" you were specifying. Here is the "box" you were searching with and the "Point" shown outside of the "box":

enter image description here

The listing shown above corrects all of these things and supplies a "Point" and "Polygon" forms with will actually meet the conditions. Try working with sites such as gejsonlint.com and others to check the validity of the data you are working with.

Upvotes: 16

Related Questions