Using GeoJSON

GeoJSON is a format for storing geographic points and polygons. MongoDB has excellent support for geospatial queries on GeoJSON objects. Let’s take a look at how you can use Mongoose to store and query GeoJSON objects.

Point Schema

The most simple structure in GeoJSON is a point. Below is an example point representing the approximate location of San Francisco. Note that longitude comes first in a GeoJSON coordinate array, not latitude.

  1. {
  2. "type" : "Point",
  3. "coordinates" : [
  4. -122.5,
  5. 37.7
  6. ]
  7. }

Below is an example of a Mongoose schema where location is a point.

  1. const citySchema = new mongoose.Schema({
  2. name: String,
  3. location: {
  4. type: {
  5. type: String, // Don't do `{ location: { type: String } }`
  6. enum: ['Point'], // 'location.type' must be 'Point'
  7. required: true
  8. },
  9. coordinates: {
  10. type: [Number],
  11. required: true
  12. }
  13. }
  14. });

Using subdocuments, you can define a common pointSchema and reuse it everywhere you want to store a GeoJSON point.

  1. const pointSchema = new mongoose.Schema({
  2. type: {
  3. type: String,
  4. enum: ['Point'],
  5. required: true
  6. },
  7. coordinates: {
  8. type: [Number],
  9. required: true
  10. }
  11. });
  12. const citySchema = new mongoose.Schema({
  13. name: String,
  14. location: {
  15. type: pointSchema,
  16. required: true
  17. }
  18. });

Polygon Schema

GeoJSON polygons let you define an arbitrary shape on a map. For example, the below polygon is a GeoJSON rectangle that approximates the border of the state of Colorado.

  1. {
  2. "type": "Polygon",
  3. "coordinates": [[
  4. [-109, 41],
  5. [-102, 41],
  6. [-102, 37],
  7. [-109, 37],
  8. [-109, 41]
  9. ]]
  10. }

Polygons are tricky because they use triple nested arrays. Below is how you create a Mongoose schema where coordinates is a triple nested array of numbers.

  1. const polygonSchema = new mongoose.Schema({
  2. type: {
  3. type: String,
  4. enum: ['Polygon'],
  5. required: true
  6. },
  7. coordinates: {
  8. type: [[[Number]]], // Array of arrays of arrays of numbers
  9. required: true
  10. }
  11. });
  12. const citySchema = new mongoose.Schema({
  13. name: String,
  14. location: polygonSchema
  15. });

Geospatial Queries with Mongoose

Mongoose queries support the same geospatial query operators that the MongoDB driver does. For example, the below script saves a city document those location property is a GeoJSON point representing the city of Denver, Colorado. It then queries for all documents within a polygon representing the state of Colorado using the MongoDB $geoWithin operator.

GeoJSON - 图1

  1. const City = db.model('City', new Schema({
  2. name: String,
  3. location: pointSchema
  4. }));
  5. const colorado = {
  6. type: 'Polygon',
  7. coordinates: [[
  8. [-109, 41],
  9. [-102, 41],
  10. [-102, 37],
  11. [-109, 37],
  12. [-109, 41]
  13. ]]
  14. };
  15. const denver = { type: 'Point', coordinates: [-104.9903, 39.7392] };
  16. return City.create({ name: 'Denver', location: denver }).
  17. then(() => City.findOne({
  18. location: {
  19. $geoWithin: {
  20. $geometry: colorado
  21. }
  22. }
  23. })).
  24. then(doc => assert.equal(doc.name, 'Denver'));

Mongoose also has a within() helper that’s a shorthand for $geoWithin.

  1. const denver = { type: 'Point', coordinates: [-104.9903, 39.7392] };
  2. return City.create({ name: 'Denver', location: denver }).
  3. then(() => City.findOne().where('location').within(colorado)).
  4. then(doc => assert.equal(doc.name, 'Denver'));

Geospatial Indexes

MongoDB supports 2dsphere indexes for speeding up geospatial queries. Here’s how you can define a 2dsphere index on a GeoJSON point:

  1. const denver = { type: 'Point', coordinates: [-104.9903, 39.7392] };
  2. const City = db.model('City', new Schema({
  3. name: String,
  4. location: {
  5. type: pointSchema,
  6. index: '2dsphere' // Create a special 2dsphere index on `City.location`
  7. }
  8. }));
  9. return City.create({ name: 'Denver', location: denver }).
  10. then(() => City.findOne().where('location').within(colorado)).
  11. then(doc => assert.equal(doc.name, 'Denver'));

You can also define a geospatial index using the Schema#index() function as shown below.

  1. citySchema.index({ location: '2dsphere' });

MongoDB’s $near query operator and $geoNear aggregation stage require a 2dsphere index.